From fc82fd7bbb2197ac02a4d3e59bd202cad8d0ffe8 Mon Sep 17 00:00:00 2001 From: Mohit Singh Date: Sat, 7 Mar 2026 01:34:35 +0530 Subject: [PATCH 1/6] fix: preserve mirror URL path when rewriting requests Replace Url::join() with manual path concatenation in the mirror middleware. Url::join() follows RFC 3986 and replaces the last path segment of the base URL instead of appending when no trailing slash is present. This caused mirror URLs with path components (e.g. https://my-custom-url/channel) to lose their path during rewriting. Fixes #1283 --- .../src/mirror_middleware.rs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/rattler_networking/src/mirror_middleware.rs b/crates/rattler_networking/src/mirror_middleware.rs index f36ee396b..d79d9dfc2 100644 --- a/crates/rattler_networking/src/mirror_middleware.rs +++ b/crates/rattler_networking/src/mirror_middleware.rs @@ -118,7 +118,16 @@ impl Middleware for MirrorMiddleware { }; let mirror = &selected_mirror.mirror; - let selected_url = mirror.url.join(url_rest).unwrap(); + let selected_url = { + let mut u = mirror.url.clone(); + let base_path = u.path().trim_end_matches('/'); + if url_rest.is_empty() { + u.set_path(&format!("{base_path}/")); + } else { + u.set_path(&format!("{base_path}/{url_rest}")); + } + u + }; // Short-circuit if the mirror does not support the file type if url_rest.ends_with(".json.zst") && mirror.no_zstd { @@ -301,4 +310,48 @@ mod test { len = path.0.len(); } } + + #[tokio::test] + async fn test_mirror_middleware_path_rewrite() { + // Start a server that serves at /channel/count + let state = String::from("mirror server"); + let router = Router::new() + .route("/channel/count", get(count)) + .with_state(state); + + let addr = SocketAddr::new([127, 0, 0, 1].into(), 0); + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); + let addr = listener.local_addr().unwrap(); + tokio::spawn(axum::serve(listener, router.into_make_service()).into_future()); + + let mirror_url: Url = format!("http://{}:{}/channel", addr.ip(), addr.port()) + .parse() + .unwrap(); + + let mut mirror_map = std::collections::HashMap::new(); + + // Upstream key includes a path segment (e.g. conda-forge) + // Mirror URL also has a path segment (e.g. channel) + // The mirror path must fully replace the upstream path. + mirror_map.insert( + "https://prefix.dev/conda-forge".parse().unwrap(), + vec![mirror_setting(mirror_url)], + ); + + let middleware = MirrorMiddleware::from_map(mirror_map); + let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new()) + .with(middleware) + .build(); + + // Request to upstream: https://prefix.dev/conda-forge/count + // Should be rewritten to: http://127.0.0.1:PORT/channel/count + let res = client + .get("https://prefix.dev/conda-forge/count") + .send() + .await + .unwrap(); + assert!(res.status().is_success(), "status: {}", res.status()); + let body = res.text().await.unwrap(); + assert_eq!(body, "Hi from counter: mirror server"); + } } From 5de5e0a16903fdb92d05fc547df77f65caace330 Mon Sep 17 00:00:00 2001 From: Mohit Singh Date: Sat, 7 Mar 2026 01:40:26 +0530 Subject: [PATCH 2/6] fix: preserve mirror URL path when rewriting requests (#1283) Replace `Url::join()` with explicit path concatenation in the mirror middleware. `Url::join()` follows RFC3986 relative resolution which replaces the last path segment when the base URL has no trailing slash. This caused mirror URLs with path components (e.g. https://my-custom-url/channel) to lose their path during rewriting. The new implementation preserves the mirror base path and safely appends the remaining request path. Adds a regression test verifying correct path rewriting. --- crates/rattler_networking/src/mirror_middleware.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/rattler_networking/src/mirror_middleware.rs b/crates/rattler_networking/src/mirror_middleware.rs index d79d9dfc2..401491801 100644 --- a/crates/rattler_networking/src/mirror_middleware.rs +++ b/crates/rattler_networking/src/mirror_middleware.rs @@ -120,12 +120,13 @@ impl Middleware for MirrorMiddleware { let mirror = &selected_mirror.mirror; let selected_url = { let mut u = mirror.url.clone(); - let base_path = u.path().trim_end_matches('/'); - if url_rest.is_empty() { - u.set_path(&format!("{base_path}/")); + let base = u.path().trim_end_matches('/'); + let new_path = if base.is_empty() { + format!("/{url_rest}") } else { - u.set_path(&format!("{base_path}/{url_rest}")); - } + format!("{base}/{url_rest}") + }; + u.set_path(&new_path); u }; From 5e0558a489982ba734f7d820d2790e73238420ce Mon Sep 17 00:00:00 2001 From: Mohit Singh Date: Wed, 11 Mar 2026 17:11:07 +0530 Subject: [PATCH 3/6] refactor(networking): use UrlWithTrailingSlash for mirror URL construction --- Cargo.lock | 1 + crates/rattler_networking/Cargo.toml | 1 + crates/rattler_networking/src/mirror_middleware.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0357b655c..adcab959e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5198,6 +5198,7 @@ dependencies = [ "itertools 0.14.0", "keyring", "netrc-rs", + "rattler_conda_types", "rattler_config", "reqwest 0.12.28", "reqwest-middleware", diff --git a/crates/rattler_networking/Cargo.toml b/crates/rattler_networking/Cargo.toml index 21c6df169..d29471f2b 100644 --- a/crates/rattler_networking/Cargo.toml +++ b/crates/rattler_networking/Cargo.toml @@ -22,6 +22,7 @@ system-integration = ["keyring", "netrc-rs", "dirs"] features = ["gcs", "s3"] [dependencies] +rattler_conda_types = { path = "../rattler_conda_types" } anyhow = { workspace = true } async-once-cell = { workspace = true } async-trait = { workspace = true } diff --git a/crates/rattler_networking/src/mirror_middleware.rs b/crates/rattler_networking/src/mirror_middleware.rs index 401491801..57235a3d9 100644 --- a/crates/rattler_networking/src/mirror_middleware.rs +++ b/crates/rattler_networking/src/mirror_middleware.rs @@ -1,4 +1,5 @@ //! Middleware to handle mirrors +use rattler_conda_types::utils::url_with_trailing_slash::UrlWithTrailingSlash; use std::{ collections::HashMap, sync::atomic::{self, AtomicUsize}, From 9deab3bb46cb937d464369edbc36be581a6e0281 Mon Sep 17 00:00:00 2001 From: Mohit Singh Date: Wed, 11 Mar 2026 19:04:17 +0530 Subject: [PATCH 4/6] fix(networking): use workspace dependency for rattler_conda_types --- crates/rattler_networking/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rattler_networking/Cargo.toml b/crates/rattler_networking/Cargo.toml index d29471f2b..c98542057 100644 --- a/crates/rattler_networking/Cargo.toml +++ b/crates/rattler_networking/Cargo.toml @@ -22,7 +22,7 @@ system-integration = ["keyring", "netrc-rs", "dirs"] features = ["gcs", "s3"] [dependencies] -rattler_conda_types = { path = "../rattler_conda_types" } +rattler_conda_types = { workspace = true } anyhow = { workspace = true } async-once-cell = { workspace = true } async-trait = { workspace = true } From be4e008403855df87b5300237f971baec2a7de19 Mon Sep 17 00:00:00 2001 From: Mohit Singh Date: Wed, 11 Mar 2026 19:15:54 +0530 Subject: [PATCH 5/6] fix(networking): properly use UrlWithTrailingSlash for mirror URL construction --- crates/rattler_networking/src/mirror_middleware.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/crates/rattler_networking/src/mirror_middleware.rs b/crates/rattler_networking/src/mirror_middleware.rs index 57235a3d9..7ba967fab 100644 --- a/crates/rattler_networking/src/mirror_middleware.rs +++ b/crates/rattler_networking/src/mirror_middleware.rs @@ -119,17 +119,9 @@ impl Middleware for MirrorMiddleware { }; let mirror = &selected_mirror.mirror; - let selected_url = { - let mut u = mirror.url.clone(); - let base = u.path().trim_end_matches('/'); - let new_path = if base.is_empty() { - format!("/{url_rest}") - } else { - format!("{base}/{url_rest}") - }; - u.set_path(&new_path); - u - }; + + let base = UrlWithTrailingSlash::from(mirror.url.clone()); + let selected_url = base.as_ref().join(url_rest).unwrap(); // Short-circuit if the mirror does not support the file type if url_rest.ends_with(".json.zst") && mirror.no_zstd { From dd2a12eb75addad939eb57438b25c115c88ece5a Mon Sep 17 00:00:00 2001 From: Mohit Singh Date: Wed, 11 Mar 2026 19:58:34 +0530 Subject: [PATCH 6/6] trigger ci