diff --git a/src/cargo/sources/registry/http_remote.rs b/src/cargo/sources/registry/http_remote.rs index c8b1ed2a3a3..6b3fb6dd1b4 100644 --- a/src/cargo/sources/registry/http_remote.rs +++ b/src/cargo/sources/registry/http_remote.rs @@ -18,7 +18,6 @@ use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::errors::HttpNotSuccessful; use crate::util::interning::InternedString; -use crate::util::network::http_async::ResponsePartsExtensions; use crate::util::network::retry::Retry; use crate::util::network::retry::RetryResult; use anyhow::Context as _; @@ -27,6 +26,7 @@ use cargo_util::paths; use futures::lock::Mutex; use http::HeaderName; use http::HeaderValue; +use http::Response; use std::cell::Cell; use std::cell::RefCell; use std::collections::HashSet; @@ -601,17 +601,10 @@ impl<'gctx> HttpBackend<'gctx> { } } - let mut err = Err(HttpNotSuccessful { - code: http::StatusCode::UNAUTHORIZED.as_u16() as u32, - body: body, - url: full_url, - ip: None, - headers: response - .headers - .iter() - .map(|(k, v)| format!("{}: {}", k, v.to_str().unwrap_or_default())) - .collect(), - } + let mut err = Err(HttpNotSuccessful::new_from_response( + Response::from_parts(response, body), + &full_url, + ) .into()); if self.auth_required.get() { let auth_error = auth::AuthorizationError::new( @@ -624,17 +617,10 @@ impl<'gctx> HttpBackend<'gctx> { } err } - code => Err(HttpNotSuccessful { - code: code.as_u16() as u32, - body: body, - url: full_url, - ip: response.client_ip().map(str::to_owned), - headers: response - .headers - .iter() - .map(|(k, v)| format!("{}: {}", k, v.to_str().unwrap_or_default())) - .collect(), - } + _ => Err(HttpNotSuccessful::new_from_response( + Response::from_parts(response, body), + &full_url, + ) .into()), } } diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index 254a61bcf47..69c2ba6c25c 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -1,8 +1,11 @@ use anyhow::Error; use curl::easy::Easy; +use http::Response; use std::fmt::{self, Write}; use std::path::PathBuf; +use crate::util::network::http_async::ResponsePartsExtensions; + use super::truncate_with_ellipsis; pub type CargoResult = anyhow::Result; @@ -59,6 +62,24 @@ impl HttpNotSuccessful { } } + pub fn new_from_response(response: Response>, url: &str) -> HttpNotSuccessful { + let ip = response.client_ip().map(str::to_string); + let url = response.effective_url().unwrap_or(url).to_string(); + let (head, body) = response.into_parts(); + let headers = head + .headers + .into_iter() + .filter_map(|(k, v)| Some(format!("{}: {}", k?.as_str(), v.to_str().ok()?))) + .collect(); + HttpNotSuccessful { + code: head.status.as_u16() as u32, + url, + ip, + body, + headers, + } + } + /// Renders the error in a compact form. pub fn display_short(&self) -> String { self.render(false) diff --git a/src/cargo/util/network/http_async.rs b/src/cargo/util/network/http_async.rs index 50054f71aab..d17aba65121 100644 --- a/src/cargo/util/network/http_async.rs +++ b/src/cargo/util/network/http_async.rs @@ -25,10 +25,10 @@ type HttpResult = std::result::Result; #[derive(Debug, Clone, thiserror::Error)] #[non_exhaustive] pub enum Error { - #[error("curl multi failed")] + #[error(transparent)] Multi(#[from] curl::MultiError), - #[error("curl failed")] + #[error(transparent)] Easy(#[from] curl::Error), #[error("failed to convert header value of `{name}` to string: {bytes:?}")] @@ -212,6 +212,7 @@ impl WorkerServer { // Would be nice to set HTTP version via `response.version_mut()`, but `curl` doesn't have it exposed. let extensions = Extensions { client_ip: easy.primary_ip().ok().flatten().map(str::to_string), + effective_url: easy.effective_url().ok().flatten().map(str::to_string), }; response.extensions_mut().insert(extensions); let _ = sender.send(result.map(|()| response).map_err(Into::into)); @@ -340,10 +341,12 @@ impl Handler for Collector { #[derive(Clone)] struct Extensions { client_ip: Option, + effective_url: Option, } pub trait ResponsePartsExtensions { fn client_ip(&self) -> Option<&str>; + fn effective_url(&self) -> Option<&str>; } impl ResponsePartsExtensions for http::response::Parts { @@ -352,6 +355,12 @@ impl ResponsePartsExtensions for http::response::Parts { .get::() .and_then(|extensions| extensions.client_ip.as_deref()) } + + fn effective_url(&self) -> Option<&str> { + self.extensions + .get::() + .and_then(|extensions| extensions.effective_url.as_deref()) + } } impl ResponsePartsExtensions for Response { @@ -360,6 +369,12 @@ impl ResponsePartsExtensions for Response { .get::() .and_then(|extensions| extensions.client_ip.as_deref()) } + + fn effective_url(&self) -> Option<&str> { + self.extensions() + .get::() + .and_then(|extensions| extensions.effective_url.as_deref()) + } } /// Splits HTTP `HEADER: VALUE` to a tuple. diff --git a/tests/testsuite/registry_auth.rs b/tests/testsuite/registry_auth.rs index 6954c72b0d7..844b5d5a7e4 100644 --- a/tests/testsuite/registry_auth.rs +++ b/tests/testsuite/registry_auth.rs @@ -234,7 +234,7 @@ Caused by: [NOTE] the token does not include an authentication scheme Caused by: - failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json` ([..]), got 401 body: Unauthorized message from server. @@ -276,7 +276,7 @@ Caused by: [NOTE] the token does not include an authentication scheme Caused by: - failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json` ([..]), got 401 body: Unauthorized message from server. @@ -321,7 +321,7 @@ Caused by: [NOTE] the token does not include an authentication scheme Caused by: - failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json` ([..]), got 401 body: Unauthorized message from server. @@ -416,7 +416,7 @@ Caused by: [NOTE] the token does not include an authentication scheme Caused by: - failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json` ([..]), got 401 body: Unauthorized message from server. @@ -480,7 +480,7 @@ Caused by: or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN Caused by: - failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json`, got 401 + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/config.json` ([..]), got 401 body: Unauthorized message from server.