diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs index f29bdb8fdb9..a5603a2939c 100644 --- a/src/cargo/sources/git/utils.rs +++ b/src/cargo/sources/git/utils.rs @@ -600,7 +600,7 @@ pub fn fetch(repo: &mut git2::Repository, opts.remote_callbacks(cb) .download_tags(git2::AutotagOption::All); - network::with_retry(config, || { + network::with_retry(config, url, || { debug!("initiating fetch of {} from {}", refspec, url); remote.fetch(&[refspec], Some(&mut opts), None) .map_err(CargoError::from) diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs index 489d001b952..abb0d82755d 100644 --- a/src/cargo/sources/registry/remote.rs +++ b/src/cargo/sources/registry/remote.rs @@ -216,14 +216,13 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { // TODO: don't download into memory, but ensure that if we ctrl-c a // download we should resume either from the start or the middle // on the next time - let url = url.to_string(); let mut handle = self.config.http()?.borrow_mut(); handle.get(true)?; - handle.url(&url)?; + handle.url(&url.to_string())?; handle.follow_location(true)?; let mut state = Sha256::new(); let mut body = Vec::new(); - network::with_retry(self.config, || { + network::with_retry(self.config, &url, || { state = Sha256::new(); body = Vec::new(); let mut pb = Progress::new("Fetch", self.config); @@ -242,8 +241,10 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { } let code = handle.response_code()?; if code != 200 && code != 0 { - let url = handle.effective_url()?.unwrap_or(&url); - Err(HttpNot200 { code, url: url.to_string() }.into()) + let url = handle.effective_url()? + .map(|url| url.to_string()) + .unwrap_or_else(|| url.to_string()); + Err(HttpNot200 { code, url }.into()) } else { Ok(()) } diff --git a/src/cargo/util/network.rs b/src/cargo/util/network.rs index 170805695b3..19a3ddcce2f 100644 --- a/src/cargo/util/network.rs +++ b/src/cargo/util/network.rs @@ -1,5 +1,6 @@ use curl; use git2; +use url::Url; use failure::Error; @@ -33,6 +34,35 @@ fn maybe_spurious(err: &Error) -> bool { false } + +/// Suggest the user to update their windows 7 to support modern TLS versions. +/// See https://github.com/rust-lang/cargo/issues/5066 for details. +#[cfg(windows)] +fn should_warn_about_old_tls_for_win7(url: &Url, err: &Error) -> bool { + let is_github = url.host_str() == Some("github.com"); + let is_cert_error = err.causes() + .filter_map(|e| e.downcast_ref::()) + .find(|e| e.class() == git2::ErrorClass::Net && e.code() == git2::ErrorCode::Certificate) + .is_some(); + is_github && is_cert_error +} + +#[cfg(not(windows))] +fn should_warn_about_old_tls_for_win7(_url: &Url, _err: &Error) -> bool { + false +} + +const WIN7_TLS_WARNING: &str = "\ +Certificate check failure might be caused by outdated TLS on older versions of Windows. +If you are using Windows 7, Windows Server 2008 R2 or Windows Server 2012, +please follow these instructions to enable more secure TLS: + + https://support.microsoft.com/en-us/help/3140245/ + +See https://github.com/rust-lang/cargo/issues/5066 for details. +"; + + /// Wrapper method for network call retry logic. /// /// Retry counts provided by Config object `net.retry`. Config shell outputs @@ -44,9 +74,9 @@ fn maybe_spurious(err: &Error) -> bool { /// /// ```ignore /// use util::network; -/// cargo_result = network.with_retry(&config, || something.download()); +/// cargo_result = network::with_retry(&config, || something.download()); /// ``` -pub fn with_retry(config: &Config, mut callback: F) -> CargoResult +pub fn with_retry(config: &Config, url: &Url, mut callback: F) -> CargoResult where F: FnMut() -> CargoResult { let mut remaining = config.net_retry()?; @@ -54,9 +84,14 @@ pub fn with_retry(config: &Config, mut callback: F) -> CargoResult match callback() { Ok(ret) => return Ok(ret), Err(ref e) if maybe_spurious(e) && remaining > 0 => { - let msg = format!("spurious network error ({} tries \ - remaining): {}", remaining, e); - config.shell().warn(msg)?; + config.shell().warn( + format!("spurious network error ({} tries remaining): {}", remaining, e) + )?; + + if should_warn_about_old_tls_for_win7(url, e) { + config.shell().warn(WIN7_TLS_WARNING)?; + } + remaining -= 1; } //todo impl from @@ -71,7 +106,8 @@ fn with_retry_repeats_the_call_then_works() { let error2 = HttpNot200 { code: 502, url: "Uri".to_string() }.into(); let mut results: Vec> = vec![Ok(()), Err(error1), Err(error2)]; let config = Config::default().unwrap(); - let result = with_retry(&config, || results.pop().unwrap()); + let url = "http://example.com".parse().unwrap(); + let result = with_retry(&config, &url, || results.pop().unwrap()); assert_eq!(result.unwrap(), ()) } @@ -87,6 +123,7 @@ fn with_retry_finds_nested_spurious_errors() { let error2 = CargoError::from(error2.context("A second chained error")); let mut results: Vec> = vec![Ok(()), Err(error1), Err(error2)]; let config = Config::default().unwrap(); - let result = with_retry(&config, || results.pop().unwrap()); + let url = "http://example.com".parse().unwrap(); + let result = with_retry(&config, &url, || results.pop().unwrap()); assert_eq!(result.unwrap(), ()) }