Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(download): remove curl backend #3788

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 0 additions & 43 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@ repository = "https://github.com/rust-lang/rustup"
build = "build.rs"

[features]
curl-backend = ["download/curl-backend"]
default = [
"curl-backend",
"reqwest-backend",
"reqwest-default-tls",
"reqwest-rustls-tls",
]

reqwest-backend = ["download/reqwest-backend"]
vendored-openssl = ['openssl/vendored']

reqwest-default-tls = ["download/reqwest-default-tls"]
Expand Down Expand Up @@ -61,7 +57,7 @@ itertools = "0.12"
libc = "0.2"
once_cell.workspace = true
opener = "0.7.0"
# `openssl` is used by `curl` or `reqwest` backend although it isn't imported by rustup: this
# `openssl` is used by or `reqwest` backend although it isn't imported by rustup: this
# allows controlling the vendoring status without exposing the presence of the download crate.
# HACK: We temporarily pin the OpenSSL version due to build issues encountered in
# https://github.com/rust-lang/rustup/pull/3668.
Expand Down Expand Up @@ -192,4 +188,4 @@ opt-level = 0

[package.metadata.cargo-all-features]
# Building with no web backend will error.
always_include_features = ["reqwest-backend", "reqwest-rustls-tls"]
always_include_features = ["reqwest-rustls-tls"]
4 changes: 2 additions & 2 deletions ci/run.bash
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ rustc -vV
cargo -vV


FEATURES=('--no-default-features' '--features' 'curl-backend,reqwest-backend,reqwest-default-tls')
FEATURES=('--no-default-features' '--features' 'reqwest-default-tls')
case "$(uname -s)" in
*NT* ) ;; # Windows NT
* ) FEATURES+=('--features' 'vendored-openssl') ;;
Expand Down Expand Up @@ -39,7 +39,7 @@ target_cargo() {
target_cargo build

download_pkg_test() {
features=('--no-default-features' '--features' 'curl-backend,reqwest-backend,reqwest-default-tls')
features=('--no-default-features' '--features' 'reqwest-default-tls')
case "$TARGET" in
# these platforms aren't supported by ring:
powerpc* ) ;;
Expand Down
7 changes: 2 additions & 5 deletions download/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ license = "MIT OR Apache-2.0"

[features]

default = ["reqwest-backend", "reqwest-rustls-tls", "reqwest-default-tls"]
default = ["reqwest-rustls-tls", "reqwest-default-tls"]

curl-backend = ["curl"]
reqwest-backend = ["reqwest", "env_proxy"]
reqwest-default-tls = ["reqwest/default-tls", "dep:once_cell"]
reqwest-rustls-tls = ["reqwest/rustls-tls-native-roots", "dep:once_cell"]

[dependencies]
anyhow.workspace = true
curl = { version = "0.4.44", optional = true }
env_proxy = { version = "0.4.1", optional = true }
env_proxy = "0.4.1"
once_cell = { workspace = true, optional = true }
reqwest = { version = "0.12", default-features = false, features = ["blocking", "gzip", "socks"], optional = true }
thiserror.workspace = true
Expand Down
4 changes: 0 additions & 4 deletions download/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ pub enum DownloadError {
Message(String),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[cfg(feature = "reqwest-backend")]
#[error(transparent)]
Reqwest(#[from] ::reqwest::Error),
#[cfg(feature = "curl-backend")]
#[error(transparent)]
CurlError(#[from] curl::Error),
}
179 changes: 9 additions & 170 deletions download/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const USER_AGENT: &str = concat!("rustup/", env!("CARGO_PKG_VERSION"));

#[derive(Debug, Copy, Clone)]
pub enum Backend {
Curl,
Reqwest(TlsBackend),
}

Expand All @@ -35,20 +34,19 @@ pub enum Event<'a> {
DownloadDataReceived(&'a [u8]),
}

type DownloadCallback<'a> = &'a dyn Fn(Event<'_>) -> Result<()>;

fn download_with_backend(
backend: Backend,
url: &Url,
resume_from: u64,
callback: &dyn Fn(Event<'_>) -> Result<()>,
callback: DownloadCallback<'_>,
) -> Result<()> {
match backend {
Backend::Curl => curl::download(url, resume_from, callback),
Backend::Reqwest(tls) => reqwest_be::download(url, resume_from, callback, tls),
}
}

type DownloadCallback<'a> = &'a dyn Fn(Event<'_>) -> Result<()>;

pub fn download_to_path_with_backend(
backend: Backend,
url: &Url,
Expand Down Expand Up @@ -141,125 +139,6 @@ pub fn download_to_path_with_backend(
})
}

#[cfg(all(not(feature = "reqwest-backend"), not(feature = "curl-backend")))]
compile_error!("Must enable at least one backend");

/// Download via libcurl; encrypt with the native (or OpenSSl) TLS
/// stack via libcurl
#[cfg(feature = "curl-backend")]
pub mod curl {
use std::cell::RefCell;
use std::str;
use std::time::Duration;

use anyhow::{Context, Result};
use curl::easy::Easy;
use url::Url;

use super::Event;
use crate::errors::*;

pub fn download(
url: &Url,
resume_from: u64,
callback: &dyn Fn(Event<'_>) -> Result<()>,
) -> Result<()> {
// Fetch either a cached libcurl handle (which will preserve open
// connections) or create a new one if it isn't listed.
//
// Once we've acquired it, reset the lifetime from 'static to our local
// scope.
thread_local!(static EASY: RefCell<Easy> = RefCell::new(Easy::new()));
EASY.with(|handle| {
let mut handle = handle.borrow_mut();

handle.url(url.as_ref())?;
handle.follow_location(true)?;
handle.useragent(super::USER_AGENT)?;

if resume_from > 0 {
handle.resume_from(resume_from)?;
} else {
// an error here indicates that the range header isn't supported by underlying curl,
// so there's nothing to "clear" - safe to ignore this error.
let _ = handle.resume_from(0);
}

// Take at most 30s to connect
handle.connect_timeout(Duration::new(30, 0))?;

{
let cberr = RefCell::new(None);
let mut transfer = handle.transfer();

// Data callback for libcurl which is called with data that's
// downloaded. We just feed it into our hasher and also write it out
// to disk.
transfer.write_function(|data| {
match callback(Event::DownloadDataReceived(data)) {
Ok(()) => Ok(data.len()),
Err(e) => {
*cberr.borrow_mut() = Some(e);
Ok(0)
}
}
})?;

// Listen for headers and parse out a `Content-Length` (case-insensitive) if it
// comes so we know how much we're downloading.
transfer.header_function(|header| {
if let Ok(data) = str::from_utf8(header) {
let prefix = "content-length: ";
if data.to_ascii_lowercase().starts_with(prefix) {
if let Ok(s) = data[prefix.len()..].trim().parse::<u64>() {
let msg = Event::DownloadContentLengthReceived(s + resume_from);
match callback(msg) {
Ok(()) => (),
Err(e) => {
*cberr.borrow_mut() = Some(e);
return false;
}
}
}
}
}
true
})?;

// If an error happens check to see if we had a filesystem error up
// in `cberr`, but we always want to punt it up.
transfer.perform().or_else(|e| {
// If the original error was generated by one of our
// callbacks, return it.
match cberr.borrow_mut().take() {
Some(cberr) => Err(cberr),
None => {
// Otherwise, return the error from curl
if e.is_file_couldnt_read_file() {
Err(e).context(DownloadError::FileNotFound)
} else {
Err(e).context("error during download")?
}
}
}
})?;
}

// If we didn't get a 20x or 0 ("OK" for files) then return an error
let code = handle.response_code()?;
match code {
0 | 200..=299 => {}
_ => {
return Err(DownloadError::HttpStatus(code).into());
}
};

Ok(())
})
}
}

#[cfg(feature = "reqwest-backend")]
pub mod reqwest_be {
#[cfg(all(
not(feature = "reqwest-rustls-tls"),
Expand Down Expand Up @@ -332,11 +211,10 @@ pub mod reqwest_be {
let catcher = || client_generic().use_rustls_tls().build();

// woah, an unwrap?!
// It's OK. This is the same as what is happening in curl.
// It's OK.
//
// The curl::Easy::new() internally assert!s that the initialized
// Easy is not null. Inside reqwest, the errors here would be from
// the TLS library returning a null pointer as well.
// Inside reqwest, the errors here would be from the TLS library
// returning a null pointer.
catcher().unwrap()
});

Expand All @@ -345,11 +223,10 @@ pub mod reqwest_be {
let catcher = || client_generic().build();

// woah, an unwrap?!
// It's OK. This is the same as what is happening in curl.
// It's OK.
//
// The curl::Easy::new() internally assert!s that the initialized
// Easy is not null. Inside reqwest, the errors here would be from
// the TLS library returning a null pointer as well.
// Inside reqwest, the errors here would be from the TLS library
// returning a null pointer.
catcher().unwrap()
});

Expand Down Expand Up @@ -423,41 +300,3 @@ pub mod reqwest_be {
}
}
}

#[cfg(not(feature = "curl-backend"))]
pub mod curl {

use anyhow::{anyhow, Result};

use super::Event;
use crate::errors::*;
use url::Url;

pub fn download(
_url: &Url,
_resume_from: u64,
_callback: &dyn Fn(Event<'_>) -> Result<()>,
) -> Result<()> {
Err(anyhow!(DownloadError::BackendUnavailable("curl")))
}
}

#[cfg(not(feature = "reqwest-backend"))]
pub mod reqwest_be {

use anyhow::{anyhow, Result};

use super::Event;
use super::TlsBackend;
use crate::errors::*;
use url::Url;

pub fn download(
_url: &Url,
_resume_from: u64,
_callback: &dyn Fn(Event<'_>) -> Result<()>,
_tls: TlsBackend,
) -> Result<()> {
Err(anyhow!(DownloadError::BackendUnavailable("reqwest")))
}
}
Loading
Loading