v0.25.1
This commit is contained in:
1 parent
87e571c170
commit
fa7abc9e53
308 files changed
+49746
-50237
No files matched your search
@@ -0,0 +1,278 @@
|
||||
mod authority_tests {
|
||||
use super::*;
|
||||
|
||||
fn validation_policy() -> crate::config::ValidationPolicy {
|
||||
crate::config::ValidationPolicy {
|
||||
tls_addr: None,
|
||||
cert_dir: None,
|
||||
data_dir: None,
|
||||
proxy_addr: "127.0.0.1:8080".parse().unwrap(),
|
||||
admin_addr: "127.0.0.1:9090".parse().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_is_limited_to_safe_methods() {
|
||||
for method in [Method::GET, Method::HEAD, Method::OPTIONS] {
|
||||
assert!(retry_safe_method(&method));
|
||||
}
|
||||
for method in [Method::POST, Method::PUT, Method::PATCH, Method::DELETE] {
|
||||
assert!(!retry_safe_method(&method));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_http2_backend_uses_http2_request_version() {
|
||||
let mut config = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
|
||||
config.vhosts[0].backends[0].protocol = crate::config::UpstreamProtocol::Http2;
|
||||
let snapshot = crate::config::compile(config, 1, &validation_policy()).unwrap();
|
||||
let route = snapshot.select("localhost", "/").unwrap();
|
||||
assert_eq!(
|
||||
upstream_request_version(route.backend.as_ref().unwrap(), false),
|
||||
Version::HTTP_2
|
||||
);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn h3_body_capable_methods_without_length_become_known_empty_for_h1() {
|
||||
use bytes::Bytes;
|
||||
for method in [Method::POST, Method::PUT, Method::PATCH, Method::DELETE] {
|
||||
let stream = futures_util::stream::empty::<Result<Bytes, std::io::Error>>();
|
||||
let mut request = Request::builder()
|
||||
.method(method.clone())
|
||||
.version(Version::HTTP_3)
|
||||
.uri("https://example.test/api2/json/item")
|
||||
.body(Body::from_stream(stream))
|
||||
.unwrap();
|
||||
assert_eq!(request.body().size_hint().exact(), None, "{method}");
|
||||
|
||||
normalize_h3_h1_unknown_body(&mut request, Version::HTTP_11)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(request.body().is_end_stream(), "{method}");
|
||||
assert_eq!(request.body().size_hint().exact(), Some(0), "{method}");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn h3_delete_unknown_nonempty_body_is_replayed_not_buffered_away() {
|
||||
use bytes::Bytes;
|
||||
let stream = futures_util::stream::iter([Ok::<_, std::io::Error>(
|
||||
Bytes::from_static(b"payload"),
|
||||
)]);
|
||||
let mut request = Request::builder()
|
||||
.method(Method::DELETE)
|
||||
.version(Version::HTTP_3)
|
||||
.uri("https://example.test/api2/json/item")
|
||||
.body(Body::from_stream(stream))
|
||||
.unwrap();
|
||||
|
||||
normalize_h3_h1_unknown_body(&mut request, Version::HTTP_11)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let body = request.into_body().collect().await.unwrap().to_bytes();
|
||||
assert_eq!(body, Bytes::from_static(b"payload"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn h3_unknown_body_is_untouched_for_http2_upstream() {
|
||||
use bytes::Bytes;
|
||||
let stream = futures_util::stream::pending::<Result<Bytes, std::io::Error>>();
|
||||
let mut request = Request::builder()
|
||||
.method(Method::DELETE)
|
||||
.version(Version::HTTP_3)
|
||||
.uri("https://example.test/api2/json/item")
|
||||
.body(Body::from_stream(stream))
|
||||
.unwrap();
|
||||
|
||||
normalize_h3_h1_unknown_body(&mut request, Version::HTTP_2)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(request.body().size_hint().exact(), None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn non_h3_unknown_body_is_untouched_for_http1_upstream() {
|
||||
use bytes::Bytes;
|
||||
for version in [Version::HTTP_11, Version::HTTP_2] {
|
||||
let stream = futures_util::stream::pending::<Result<Bytes, std::io::Error>>();
|
||||
let mut request = Request::builder()
|
||||
.method(Method::DELETE)
|
||||
.version(version)
|
||||
.uri("https://example.test/api2/json/item")
|
||||
.body(Body::from_stream(stream))
|
||||
.unwrap();
|
||||
|
||||
normalize_h3_h1_unknown_body(&mut request, Version::HTTP_11)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(request.body().size_hint().exact(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn h3_declared_delete_body_is_not_preconsumed() {
|
||||
use bytes::Bytes;
|
||||
let stream = futures_util::stream::pending::<Result<Bytes, std::io::Error>>();
|
||||
let mut request = Request::builder()
|
||||
.method(Method::DELETE)
|
||||
.version(Version::HTTP_3)
|
||||
.uri("https://example.test/api2/json/item")
|
||||
.header(http::header::CONTENT_LENGTH, "7")
|
||||
.body(Body::from_stream(stream))
|
||||
.unwrap();
|
||||
|
||||
normalize_h3_h1_unknown_body(&mut request, Version::HTTP_11)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(request.headers()[http::header::CONTENT_LENGTH], "7");
|
||||
assert_eq!(request.body().size_hint().exact(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_policy_and_gateway_errors_use_html_pages() {
|
||||
for status in [
|
||||
StatusCode::FORBIDDEN,
|
||||
StatusCode::TOO_MANY_REQUESTS,
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
StatusCode::GATEWAY_TIMEOUT,
|
||||
] {
|
||||
let response = error(status, "internal detail\n");
|
||||
assert_eq!(response.status(), status);
|
||||
assert_eq!(
|
||||
response.headers().get("content-type").unwrap(),
|
||||
"text/html; charset=utf-8"
|
||||
);
|
||||
assert_eq!(response.headers().get("cache-control").unwrap(), "no-store");
|
||||
}
|
||||
let response = error(StatusCode::BAD_GATEWAY, "internal detail\n");
|
||||
assert_ne!(
|
||||
response.headers().get("content-type").unwrap(),
|
||||
"text/html; charset=utf-8"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn locally_generated_pages_remove_cache_metadata() {
|
||||
let mut response = error(StatusCode::BAD_GATEWAY, "internal detail\n");
|
||||
for (name, value) in [
|
||||
("etag", "\"old\""),
|
||||
("x-cache", "HIT"),
|
||||
("x-cache-reason", "origin"),
|
||||
("cache-status", "Origin; hit"),
|
||||
] {
|
||||
response.headers_mut().insert(name, value.parse().unwrap());
|
||||
}
|
||||
enforce_local_page_no_cache(&mut response);
|
||||
assert_eq!(response.headers().get("cache-control").unwrap(), "no-store");
|
||||
for name in ["etag", "x-cache", "x-cache-reason", "cache-status"] {
|
||||
assert!(!response.headers().contains_key(name), "{name}");
|
||||
}
|
||||
assert!(response.extensions().get::<LocalGeneratedResponse>().is_some());
|
||||
}
|
||||
#[test]
|
||||
fn http2_uses_authority_and_rejects_conflicting_host() {
|
||||
let mut request = Request::builder()
|
||||
.version(Version::HTTP_2)
|
||||
.uri("https://app.localhost/test")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
assert_eq!(effective_host(&request, true).unwrap(), "app.localhost");
|
||||
request
|
||||
.headers_mut()
|
||||
.insert(HOST, "different.localhost".parse().unwrap());
|
||||
assert!(effective_host(&request, true).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn legacy_websocket_origin_matches_upstream_scheme_and_host() {
|
||||
let origin = legacy_websocket_origin(
|
||||
"https://ilo.internal:443",
|
||||
&"10.0.0.5:443".parse().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(origin, "https://10.0.0.5:443");
|
||||
assert!(legacy_websocket_origin("not-an-origin", &"10.0.0.5".parse().unwrap()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_ssl_location_preserves_path_query_and_https_port() {
|
||||
let uri: Uri = "/api/items?x=1&next=%2Fhome".parse().unwrap();
|
||||
assert_eq!(
|
||||
force_ssl_location("app.example.test", &uri, 443).unwrap(),
|
||||
"https://app.example.test/api/items?x=1&next=%2Fhome"
|
||||
);
|
||||
assert_eq!(
|
||||
force_ssl_location("app.example.test", &uri, 8443).unwrap(),
|
||||
"https://app.example.test:8443/api/items?x=1&next=%2Fhome"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn http1_absolute_form_and_h2_wrong_scheme_are_rejected() {
|
||||
let request = Request::builder()
|
||||
.uri("http://app.localhost/")
|
||||
.header(HOST, "app.localhost")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
assert!(effective_host(&request, false).is_err());
|
||||
let request = Request::builder()
|
||||
.version(Version::HTTP_2)
|
||||
.uri("http://app.localhost/")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
assert!(effective_host(&request, true).is_err());
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct NestedDiagnosticError {
|
||||
source: std::io::Error,
|
||||
}
|
||||
impl std::fmt::Display for NestedDiagnosticError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("connector wrapper")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for NestedDiagnosticError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
Some(&self.source)
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn upstream_error_chain_keeps_nested_connector_cause() {
|
||||
let error = NestedDiagnosticError {
|
||||
source: std::io::Error::new(
|
||||
std::io::ErrorKind::ConnectionRefused,
|
||||
"connection refused by origin",
|
||||
),
|
||||
};
|
||||
let detail = format_error_chain(&error);
|
||||
assert!(detail.contains("connector wrapper"));
|
||||
assert!(detail.contains("connection refused by origin"));
|
||||
}
|
||||
#[test]
|
||||
fn upstream_error_detail_truncation_preserves_utf8_boundaries() {
|
||||
let mut detail = "ą".repeat(MAX_UPSTREAM_ERROR_DETAIL);
|
||||
truncate_utf8(&mut detail, MAX_UPSTREAM_ERROR_DETAIL);
|
||||
assert!(detail.len() <= MAX_UPSTREAM_ERROR_DETAIL);
|
||||
assert!(std::str::from_utf8(detail.as_bytes()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backend_diagnostics_are_opt_in_and_strip_spoofed_values() {
|
||||
let mut response = Response::new(Body::empty());
|
||||
response.headers_mut().insert("x-rust-proxy-backend", HeaderValue::from_static("spoofed"));
|
||||
response.headers_mut().insert("x-rust-proxy-backend-time", HeaderValue::from_static("legacy-spoof"));
|
||||
response.headers_mut().insert("x-rustproxy-backend-time", HeaderValue::from_static("999ms"));
|
||||
apply_backend_diagnostic_values(&mut response, false, false);
|
||||
assert!(response.headers().get("x-rust-proxy-backend").is_none());
|
||||
assert!(response.headers().get("x-rust-proxy-backend-time").is_none());
|
||||
assert!(response.headers().get("x-rustproxy-backend-time").is_none());
|
||||
|
||||
response.extensions_mut().insert(OriginBackend("origin-a".into()));
|
||||
response.extensions_mut().insert(OriginTiming(Duration::from_millis(12)));
|
||||
apply_backend_diagnostic_values(&mut response, true, true);
|
||||
assert_eq!(response.headers().get("x-rust-proxy-backend").unwrap(), "origin-a");
|
||||
assert_eq!(response.headers().get("x-rustproxy-backend-time").unwrap(), "12.00ms");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user