diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:44 +0000 |
commit | c23a457e72abe608715ac76f076f47dc42af07a5 (patch) | |
tree | 2772049aaf84b5c9d0ed12ec8d86812f7a7904b6 /vendor/gix-transport/src | |
parent | Releasing progress-linux version 1.73.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-c23a457e72abe608715ac76f076f47dc42af07a5.tar.xz rustc-c23a457e72abe608715ac76f076f47dc42af07a5.zip |
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-transport/src')
13 files changed, 163 insertions, 46 deletions
diff --git a/vendor/gix-transport/src/client/async_io/connect.rs b/vendor/gix-transport/src/client/async_io/connect.rs index fe2a5808e..67b20db03 100644 --- a/vendor/gix-transport/src/client/async_io/connect.rs +++ b/vendor/gix-transport/src/client/async_io/connect.rs @@ -1,6 +1,6 @@ pub use crate::client::non_io_types::connect::{Error, Options}; -#[cfg(any(feature = "async-std"))] +#[cfg(feature = "async-std")] pub(crate) mod function { use std::convert::TryInto; diff --git a/vendor/gix-transport/src/client/async_io/mod.rs b/vendor/gix-transport/src/client/async_io/mod.rs index 6cb1a500e..1ea85cdcf 100644 --- a/vendor/gix-transport/src/client/async_io/mod.rs +++ b/vendor/gix-transport/src/client/async_io/mod.rs @@ -9,5 +9,5 @@ pub use traits::{SetServiceResponse, Transport, TransportV2Ext}; /// pub mod connect; -#[cfg(any(feature = "async-std"))] +#[cfg(feature = "async-std")] pub use connect::function::connect; diff --git a/vendor/gix-transport/src/client/blocking_io/file.rs b/vendor/gix-transport/src/client/blocking_io/file.rs index 599f56c23..613fd2357 100644 --- a/vendor/gix-transport/src/client/blocking_io/file.rs +++ b/vendor/gix-transport/src/client/blocking_io/file.rs @@ -211,6 +211,11 @@ impl client::Transport for SpawnProcessOnDemand { }; cmd.stdin = Stdio::piped(); cmd.stdout = Stdio::piped(); + if self.path.first() == Some(&b'-') { + return Err(client::Error::AmbiguousPath { + path: self.path.clone(), + }); + } let repo_path = if self.ssh_cmd.is_some() { cmd.args.push(service.as_str().into()); gix_quote::single(self.path.as_ref()).to_os_str_lossy().into_owned() @@ -225,6 +230,7 @@ impl client::Transport for SpawnProcessOnDemand { } cmd.envs(std::mem::take(&mut self.envs)); + gix_features::trace::debug!(command = ?cmd, "gix_transport::SpawnProcessOnDemand"); let mut child = cmd.spawn().map_err(|err| client::Error::InvokeProgram { source: err, command: cmd_name.into_owned(), diff --git a/vendor/gix-transport/src/client/blocking_io/http/curl/remote.rs b/vendor/gix-transport/src/client/blocking_io/http/curl/remote.rs index 3b435423d..e6b0bee03 100644 --- a/vendor/gix-transport/src/client/blocking_io/http/curl/remote.rs +++ b/vendor/gix-transport/src/client/blocking_io/http/curl/remote.rs @@ -269,7 +269,7 @@ pub fn new() -> ( handler.send_data = Some(send); let (send, receive_headers) = pipe::unidirectional(1); handler.send_header = Some(send); - let (send_body, receive_body) = pipe::unidirectional(None); + let (send_body, receive_body) = pipe::unidirectional(0); (receive_data, receive_headers, send_body, receive_body) }; diff --git a/vendor/gix-transport/src/client/blocking_io/http/mod.rs b/vendor/gix-transport/src/client/blocking_io/http/mod.rs index 64cc892d8..fb62b8f9a 100644 --- a/vendor/gix-transport/src/client/blocking_io/http/mod.rs +++ b/vendor/gix-transport/src/client/blocking_io/http/mod.rs @@ -517,5 +517,5 @@ pub fn connect(url: gix_url::Url, desired_version: Protocol) -> Transport<Impl> } /// -#[cfg(feature = "http-client-curl")] +#[cfg(any(feature = "http-client-curl", feature = "http-client-reqwest"))] pub mod redirect; diff --git a/vendor/gix-transport/src/client/blocking_io/http/reqwest/remote.rs b/vendor/gix-transport/src/client/blocking_io/http/reqwest/remote.rs index 724528ab9..7f8e82846 100644 --- a/vendor/gix-transport/src/client/blocking_io/http/reqwest/remote.rs +++ b/vendor/gix-transport/src/client/blocking_io/http/reqwest/remote.rs @@ -3,14 +3,12 @@ use std::{ convert::TryFrom, io::{Read, Write}, str::FromStr, + sync::{atomic, Arc}, }; use gix_features::io::pipe; -use crate::client::{ - http, - http::{reqwest::Remote, traits::PostBodyDataKind}, -}; +use crate::client::http::{self, options::FollowRedirects, redirect, reqwest::Remote, traits::PostBodyDataKind}; /// The error returned by the 'remote' helper, a purely internal construct to perform http requests. #[derive(Debug, thiserror::Error)] @@ -22,6 +20,8 @@ pub enum Error { ReadPostBody(#[from] std::io::Error), #[error("Request configuration failed")] ConfigureRequest(#[from] Box<dyn std::error::Error + Send + Sync + 'static>), + #[error(transparent)] + Redirect(#[from] redirect::Error), } impl crate::IsSpuriousError for Error { @@ -40,23 +40,57 @@ impl Default for Remote { let (req_send, req_recv) = std::sync::mpsc::sync_channel(0); let (res_send, res_recv) = std::sync::mpsc::sync_channel(0); let handle = std::thread::spawn(move || -> Result<(), Error> { + let mut follow = None; + let mut redirected_base_url = None::<String>; + let allow_redirects = Arc::new(atomic::AtomicBool::new(false)); + // We may error while configuring, which is expected as part of the internal protocol. The error will be // received and the sender of the request might restart us. let client = reqwest::blocking::ClientBuilder::new() .connect_timeout(std::time::Duration::from_secs(20)) .http1_title_case_headers() + .redirect(reqwest::redirect::Policy::custom({ + let allow_redirects = allow_redirects.clone(); + move |attempt| { + if allow_redirects.load(atomic::Ordering::Relaxed) { + let curr_url = attempt.url(); + let prev_urls = attempt.previous(); + + match prev_urls.first() { + Some(prev_url) if prev_url.host_str() != curr_url.host_str() => { + // git does not want to be redirected to a different host. + attempt.stop() + } + _ => { + // emulate default git behaviour which relies on curl default behaviour apparently. + const CURL_DEFAULT_REDIRS: usize = 50; + if prev_urls.len() >= CURL_DEFAULT_REDIRS { + attempt.error("too many redirects") + } else { + attempt.follow() + } + } + } + } else { + attempt.stop() + } + } + })) .build()?; + for Request { url, + base_url, headers, upload_body_kind, config, } in req_recv { + let effective_url = redirect::swap_tails(redirected_base_url.as_deref(), &base_url, url.clone()); let mut req_builder = if upload_body_kind.is_some() { - client.post(url) + client.post(&effective_url) } else { - client.get(url) + client.get(&effective_url) } .headers(headers); let (post_body_tx, mut post_body_rx) = pipe::unidirectional(0); @@ -91,7 +125,21 @@ impl Default for Remote { } } } - let mut res = match client.execute(req).and_then(|res| res.error_for_status()) { + + let follow = follow.get_or_insert(config.follow_redirects); + allow_redirects.store( + matches!(follow, FollowRedirects::Initial | FollowRedirects::All), + atomic::Ordering::Relaxed, + ); + + if *follow == FollowRedirects::Initial { + *follow = FollowRedirects::None; + } + + let mut res = match client + .execute(req) + .and_then(reqwest::blocking::Response::error_for_status) + { Ok(res) => res, Err(err) => { let (kind, err) = match err.status() { @@ -113,6 +161,11 @@ impl Default for Remote { } }; + let actual_url = res.url().as_str(); + if actual_url != effective_url.as_str() { + redirected_base_url = redirect::base_url(actual_url, &base_url, url)?.into(); + } + let send_headers = { let headers = res.headers(); move || -> std::io::Result<()> { @@ -152,10 +205,24 @@ impl Default for Remote { /// utilities impl Remote { + fn restore_thread_after_failure(&mut self) -> http::Error { + let err_that_brought_thread_down = self + .handle + .take() + .expect("thread handle present") + .join() + .expect("handler thread should never panic") + .expect_err("something should have gone wrong with curl (we join on error only)"); + *self = Remote::default(); + http::Error::InitHttpClient { + source: Box::new(err_that_brought_thread_down), + } + } + fn make_request( &mut self, url: &str, - _base_url: &str, + base_url: &str, headers: impl IntoIterator<Item = impl AsRef<str>>, upload_body_kind: Option<PostBodyDataKind>, ) -> Result<http::PostResponse<pipe::Reader, pipe::Reader, pipe::Writer>, http::Error> { @@ -176,14 +243,19 @@ impl Remote { None => continue, }; } - self.request + if self + .request .send(Request { url: url.to_owned(), + base_url: base_url.to_owned(), headers: header_map, upload_body_kind, config: self.config.clone(), }) - .expect("the remote cannot be down at this point"); + .is_err() + { + return Err(self.restore_thread_after_failure()); + } let Response { headers, @@ -192,15 +264,7 @@ impl Remote { } = match self.response.recv() { Ok(res) => res, Err(_) => { - let err = self - .handle - .take() - .expect("always present") - .join() - .expect("no panic") - .expect_err("no receiver means thread is down with init error"); - *self = Self::default(); - return Err(http::Error::InitHttpClient { source: Box::new(err) }); + return Err(self.restore_thread_after_failure()); } }; @@ -246,6 +310,7 @@ impl http::Http for Remote { pub(crate) struct Request { pub url: String, + pub base_url: String, pub headers: reqwest::header::HeaderMap, pub upload_body_kind: Option<PostBodyDataKind>, pub config: http::Options, diff --git a/vendor/gix-transport/src/client/blocking_io/ssh/mod.rs b/vendor/gix-transport/src/client/blocking_io/ssh/mod.rs index 7c042dc28..642aab9fd 100644 --- a/vendor/gix-transport/src/client/blocking_io/ssh/mod.rs +++ b/vendor/gix-transport/src/client/blocking_io/ssh/mod.rs @@ -8,6 +8,8 @@ use crate::{client::blocking_io, Protocol}; pub enum Error { #[error("The scheme in \"{}\" is not usable for an ssh connection", .0.to_bstring())] UnsupportedScheme(gix_url::Url), + #[error("Host name '{host}' could be mistaken for a command-line argument")] + AmbiguousHostName { host: String }, } impl crate::IsSpuriousError for Error {} @@ -37,12 +39,17 @@ pub mod invocation { /// The error returned when producing ssh invocation arguments based on a selected invocation kind. #[derive(Debug, thiserror::Error)] - #[error("The 'Simple' ssh variant doesn't support {function}")] - pub struct Error { - /// The simple command that should have been invoked. - pub command: OsString, - /// The function that was unsupported - pub function: &'static str, + #[allow(missing_docs)] + pub enum Error { + #[error("Host name '{host}' could be mistaken for a command-line argument")] + AmbiguousHostName { host: String }, + #[error("The 'Simple' ssh variant doesn't support {function}")] + Unsupported { + /// The simple command that should have been invoked. + command: OsString, + /// The function that was unsupported + function: &'static str, + }, } } @@ -105,7 +112,9 @@ pub fn connect( .stdin(Stdio::null()) .with_shell() .arg("-G") - .arg(url.host().expect("always set for ssh urls")), + .arg(url.host_argument_safe().ok_or_else(|| Error::AmbiguousHostName { + host: url.host().expect("set in ssh urls").into(), + })?), ) .status() .ok() diff --git a/vendor/gix-transport/src/client/blocking_io/ssh/program_kind.rs b/vendor/gix-transport/src/client/blocking_io/ssh/program_kind.rs index 5e9d14a82..70905829f 100644 --- a/vendor/gix-transport/src/client/blocking_io/ssh/program_kind.rs +++ b/vendor/gix-transport/src/client/blocking_io/ssh/program_kind.rs @@ -31,7 +31,6 @@ impl ProgramKind { if disallow_shell { prepare.use_shell = false; } - let host = url.host().expect("present in ssh urls"); match self { ProgramKind::Ssh => { if desired_version != Protocol::V1 { @@ -54,7 +53,7 @@ impl ProgramKind { } ProgramKind::Simple => { if url.port.is_some() { - return Err(ssh::invocation::Error { + return Err(ssh::invocation::Error::Unsupported { command: ssh_cmd.into(), function: "setting the port", }); @@ -62,8 +61,18 @@ impl ProgramKind { } }; let host_as_ssh_arg = match url.user() { - Some(user) => format!("{user}@{host}"), - None => host.into(), + Some(user) => { + let host = url.host().expect("present in ssh urls"); + format!("{user}@{host}") + } + None => { + let host = url + .host_argument_safe() + .ok_or_else(|| ssh::invocation::Error::AmbiguousHostName { + host: url.host().expect("ssh host always set").into(), + })?; + host.into() + } }; // Try to force ssh to yield english messages (for parsing later) @@ -107,7 +116,7 @@ impl ProgramKind { impl<'a> From<&'a OsStr> for ProgramKind { fn from(v: &'a OsStr) -> Self { let p = std::path::Path::new(v); - match p.file_stem().and_then(|s| s.to_str()) { + match p.file_stem().and_then(OsStr::to_str) { None => ProgramKind::Simple, Some(stem) => { if stem.eq_ignore_ascii_case("ssh") { diff --git a/vendor/gix-transport/src/client/blocking_io/ssh/tests.rs b/vendor/gix-transport/src/client/blocking_io/ssh/tests.rs index f0820d14e..4e4da7807 100644 --- a/vendor/gix-transport/src/client/blocking_io/ssh/tests.rs +++ b/vendor/gix-transport/src/client/blocking_io/ssh/tests.rs @@ -144,13 +144,28 @@ mod program_kind { assert!(call_args(kind, "ssh://user@host:43/p", Protocol::V2).ends_with("-P 43 user@host")); } } + #[test] + fn ambiguous_host_is_allowed_with_user() { + assert_eq!( + call_args(ProgramKind::Ssh, "ssh://user@-arg/p", Protocol::V2), + joined(&["ssh", "-o", "SendEnv=GIT_PROTOCOL", "user@-arg"]) + ); + } + + #[test] + fn ambiguous_host_is_disallowed() { + assert!(matches!( + try_call(ProgramKind::Ssh, "ssh://-arg/p", Protocol::V2), + Err(ssh::invocation::Error::AmbiguousHostName { host }) if host == "-arg" + )); + } #[test] fn simple_cannot_handle_any_arguments() { - match try_call(ProgramKind::Simple, "ssh://user@host:42/p", Protocol::V2) { - Err(ssh::invocation::Error { .. }) => {} - _ => panic!("BUG: unexpected outcome"), - } + assert!(matches!( + try_call(ProgramKind::Simple, "ssh://user@host:42/p", Protocol::V2), + Err(ssh::invocation::Error::Unsupported { .. }) + )); assert_eq!( call_args(ProgramKind::Simple, "ssh://user@host/p", Protocol::V2), joined(&["simple", "user@host"]), diff --git a/vendor/gix-transport/src/client/capabilities.rs b/vendor/gix-transport/src/client/capabilities.rs index 29b5504ba..21513ace6 100644 --- a/vendor/gix-transport/src/client/capabilities.rs +++ b/vendor/gix-transport/src/client/capabilities.rs @@ -65,11 +65,11 @@ impl<'a> Capability<'a> { /// Note that the caller must know whether a single or multiple values are expected, in which /// case [`values()`][Capability::values()] should be called. pub fn value(&self) -> Option<&'a BStr> { - self.0.splitn(2, |b| *b == b'=').nth(1).map(|s| s.as_bstr()) + self.0.splitn(2, |b| *b == b'=').nth(1).map(ByteSlice::as_bstr) } /// Returns the values of a capability if its [`value()`][Capability::value()] is space separated. pub fn values(&self) -> Option<impl Iterator<Item = &'a BStr>> { - self.value().map(|v| v.split(|b| *b == b' ').map(|s| s.as_bstr())) + self.value().map(|v| v.split(|b| *b == b' ').map(ByteSlice::as_bstr)) } /// Returns true if its space-separated [`value()`][Capability::value()] contains the given `want`ed capability. pub fn supports(&self, want: impl Into<&'a BStr>) -> Option<bool> { diff --git a/vendor/gix-transport/src/client/git/mod.rs b/vendor/gix-transport/src/client/git/mod.rs index 2b950b44a..d27f468ff 100644 --- a/vendor/gix-transport/src/client/git/mod.rs +++ b/vendor/gix-transport/src/client/git/mod.rs @@ -165,6 +165,21 @@ mod message { "git-upload-pack hello\\world\0host=host:404\0" ) } + + #[test] + fn with_strange_host_and_port() { + assert_eq!( + git::message::connect( + Service::UploadPack, + Protocol::V1, + b"--upload-pack=attack", + Some(&("--proxy=other-attack".into(), Some(404))), + &[] + ), + "git-upload-pack --upload-pack=attack\0host=--proxy=other-attack:404\0", + "we explicitly allow possible `-arg` arguments to be passed to the git daemon - the remote must protect against exploitation, we don't want to prevent legitimate cases" + ) + } } } diff --git a/vendor/gix-transport/src/client/non_io_types.rs b/vendor/gix-transport/src/client/non_io_types.rs index 807b22a8f..a1dbb247c 100644 --- a/vendor/gix-transport/src/client/non_io_types.rs +++ b/vendor/gix-transport/src/client/non_io_types.rs @@ -138,6 +138,8 @@ mod error { Http(#[from] HttpError), #[error(transparent)] SshInvocation(SshInvocationError), + #[error("The repository path '{path}' could be mistaken for a command-line argument")] + AmbiguousPath { path: BString }, } impl crate::IsSpuriousError for Error { diff --git a/vendor/gix-transport/src/lib.rs b/vendor/gix-transport/src/lib.rs index a098e635a..4ec2ea615 100644 --- a/vendor/gix-transport/src/lib.rs +++ b/vendor/gix-transport/src/lib.rs @@ -21,7 +21,6 @@ pub use gix_packetline as packetline; /// The version of the way client and server communicate. #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[allow(missing_docs)] pub enum Protocol { /// Version 0 is like V1, but doesn't show capabilities at all, at least when hosted without `git-daemon`. V0 = 0, @@ -86,10 +85,7 @@ pub use traits::IsSpuriousError; pub mod client; #[doc(inline)] -#[cfg(any( - feature = "blocking-client", - all(feature = "async-client", any(feature = "async-std")) -))] +#[cfg(any(feature = "blocking-client", all(feature = "async-client", feature = "async-std")))] pub use client::connect; #[cfg(all(feature = "async-client", feature = "blocking-client"))] |