diff --git a/Cargo.lock b/Cargo.lock index aefc5645bd..a552d2ad1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,7 @@ name = "rustup" version = "0.2.0" dependencies = [ "clap 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "download 0.2.0", "error-chain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -176,8 +177,10 @@ dependencies = [ "curl 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.1.0 (git+https://github.com/sfackler/rust-native-tls.git)", "openssl-sys 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls 0.1.0 (git+https://github.com/ctz/rustls.git)", "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -491,6 +494,15 @@ name = "regex-syntax" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ring" +version = "0.1.0" +source = "git+https://github.com/ctz/ring#9a468a7ac60268f4ec64b569c32bbc12d1554f6e" +dependencies = [ + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-serialize" version = "0.3.19" @@ -504,6 +516,19 @@ dependencies = [ "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustls" +version = "0.1.0" +source = "git+https://github.com/ctz/rustls.git#622dce5fdbd3b644e4e79d5d6e3dc891590121dc" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.1.0 (git+https://github.com/ctz/ring)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webpki 0.1.0 (git+https://github.com/ctz/webpki)", +] + [[package]] name = "rustup-dist" version = "0.2.0" @@ -750,6 +775,11 @@ name = "unicode-width" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "untrusted" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.1.0" @@ -805,6 +835,17 @@ dependencies = [ "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "webpki" +version = "0.1.0" +source = "git+https://github.com/ctz/webpki#c257d386d974ad5c1585984f7772ecbe3cff429f" +dependencies = [ + "ring 0.1.0 (git+https://github.com/ctz/ring)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.7" diff --git a/Cargo.toml b/Cargo.toml index 2887ed2caf..d61e710b17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,18 @@ license = "MIT OR Apache-2.0" build = "build.rs" +[features] + +default = ["curl-backend", "hyper-backend"] + +curl-backend = ["download/curl-backend"] +hyper-backend = ["download/hyper-backend"] +rustls-backend = ["download/rustls-backend"] + [dependencies] rustup-dist = { path = "src/rustup-dist", version = "0.2.0" } rustup-utils = { path = "src/rustup-utils", version = "0.2.0" } +download = { path = "src/download" } error-chain = "0.2.1" clap = "2.2.4" regex = "0.1.41" diff --git a/src/download/Cargo.toml b/src/download/Cargo.toml index f8efc5fb33..259d8321c0 100644 --- a/src/download/Cargo.toml +++ b/src/download/Cargo.toml @@ -8,18 +8,17 @@ license = "MIT/Apache-2.0" [features] -default = ["hyper-mode", "curl-mode"] +default = ["hyper-backend"] -curl-mode = ["curl"] -hyper-mode = ["hyper", "native-tls", "openssl-sys"] +curl-backend = ["curl"] +hyper-backend = ["hyper", "native-tls", "openssl-sys"] +rustls-backend = ["hyper", "rustls", "lazy_static"] [dependencies] error-chain = "0.2.1" url = "1.1" - -[dependencies.curl] -version = "0.3" -optional = true +curl = { version = "0.3", optional = true } +lazy_static = { version = "0.2", optional = true } [dependencies.hyper] version = "0.9.8" @@ -33,3 +32,6 @@ optional = true [target.'cfg(not(any(target_os = "windows", target_os = "macos")))'.dependencies] openssl-sys = { version = "0.7.11", optional = true } +[dependencies.rustls] +git = "https://github.com/ctz/rustls.git" +optional = true diff --git a/src/download/src/lib.rs b/src/download/src/lib.rs index 8367c2ad24..e172a5d1f0 100644 --- a/src/download/src/lib.rs +++ b/src/download/src/lib.rs @@ -4,10 +4,20 @@ extern crate error_chain; extern crate url; +#[cfg(feature = "rustls-backend")] +#[macro_use] +extern crate lazy_static; + +use url::Url; +use std::path::Path; + mod errors; pub use errors::*; -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] +pub enum Backend { Curl, Hyper, Rustls } + +#[derive(Debug, Copy, Clone)] pub enum Event<'a> { /// Received the Content-Length of the to-be downloaded data. DownloadContentLengthReceived(u64), @@ -15,9 +25,95 @@ pub enum Event<'a> { DownloadDataReceived(&'a [u8]), } +const BACKENDS: &'static [Backend] = &[ + Backend::Curl, + Backend::Hyper, + Backend::Rustls +]; + +pub fn download(url: &Url, + callback: &Fn(Event) -> Result<()>) + -> Result<()> { + for &backend in BACKENDS { + match download_with_backend(backend, url, callback) { + Err(Error(ErrorKind::BackendUnavailable(_), _)) => (), + Err(e) => return Err(e), + Ok(()) => return Ok(()), + } + } + + Err("no working backends".into()) +} + +pub fn download_to_path(url: &Url, + path: &Path, + callback: Option<&Fn(Event) -> Result<()>>) + -> Result<()> { + for &backend in BACKENDS { + match download_to_path_with_backend(backend, url, path, callback) { + Err(Error(ErrorKind::BackendUnavailable(_), _)) => (), + Err(e) => return Err(e), + Ok(()) => return Ok(()), + } + } + + Err("no working backends".into()) +} + +pub fn download_with_backend(backend: Backend, + url: &Url, + callback: &Fn(Event) -> Result<()>) + -> Result<()> { + match backend { + Backend::Curl => curl::download(url, callback), + Backend::Hyper => hyper::download(url, callback), + Backend::Rustls => rustls::download(url, callback), + } +} + +pub fn download_to_path_with_backend( + backend: Backend, + url: &Url, + path: &Path, + callback: Option<&Fn(Event) -> Result<()>>) + -> Result<()> +{ + use std::cell::RefCell; + use std::fs::{self, File}; + use std::io::Write; + + || -> Result<()> { + let file = RefCell::new(try!(File::create(&path).chain_err( + || "error creating file for download"))); + + try!(download_with_backend(backend, url, &|event| { + if let Event::DownloadDataReceived(data) = event { + try!(file.borrow_mut().write_all(data) + .chain_err(|| "unable to write download to disk")); + } + match callback { + Some(cb) => cb(event), + None => Ok(()) + } + })); + + try!(file.borrow_mut().sync_data() + .chain_err(|| "unable to sync download to disk")); + + Ok(()) + }().map_err(|e| { + if path.is_file() { + // FIXME ignoring compound errors + let _ = fs::remove_file(path); + } + + e + }) +} + /// Download via libcurl; encrypt with the native (or OpenSSl) TLS /// stack via libcurl -#[cfg(feature = "curl-mode")] +#[cfg(feature = "curl-backend")] pub mod curl { extern crate curl; @@ -122,7 +218,7 @@ pub mod curl { /// Download via hyper; encrypt with the native (or OpenSSl) TLS /// stack via native-tls -#[cfg(feature = "hyper-mode")] +#[cfg(feature = "hyper-backend")] pub mod hyper { extern crate hyper; @@ -131,157 +227,329 @@ pub mod hyper { extern crate native_tls; use super::Event; - use std::fs; use std::io; use std::time::Duration; use url::Url; use errors::*; + use hyper_base; + use self::hyper::error::Result as HyperResult; + use self::hyper::net::{SslClient, NetworkStream}; + use std::io::Result as IoResult; + use std::io::{Read, Write}; + use std::net::{SocketAddr, Shutdown}; + use std::sync::{Arc, Mutex, MutexGuard}; - fn proxy_from_env(url: &Url) -> Option<(String, u16)> { - use std::env::var_os; + pub fn download(url: &Url, + callback: &Fn(Event) -> Result<()>) + -> Result<()> { + hyper_base::download::(url, callback) + } - let mut maybe_https_proxy = var_os("https_proxy").map(|ref v| v.to_str().unwrap_or("").to_string()); - if maybe_https_proxy.is_none() { - maybe_https_proxy = var_os("HTTPS_PROXY").map(|ref v| v.to_str().unwrap_or("").to_string()); + struct NativeSslClient; + + impl hyper_base::NewSslClient for NativeSslClient { + fn new() -> Self { NativeSslClient } + fn maybe_init_certs() { maybe_init_certs() } + } + + impl SslClient for NativeSslClient { + type Stream = NativeSslStream; + + fn wrap_client(&self, stream: T, host: &str) -> HyperResult { + use self::native_tls::ClientBuilder as TlsClientBuilder; + use self::hyper::error::Error as HyperError; + + let mut ssl_builder = try!(TlsClientBuilder::new() + .map_err(|e| HyperError::Ssl(Box::new(e)))); + let ssl_stream = try!(ssl_builder.handshake(host, stream) + .map_err(|e| HyperError::Ssl(Box::new(e)))); + + Ok(NativeSslStream(Arc::new(Mutex::new(ssl_stream)))) } - let maybe_http_proxy = var_os("http_proxy").map(|ref v| v.to_str().unwrap_or("").to_string()); - let mut maybe_all_proxy = var_os("all_proxy").map(|ref v| v.to_str().unwrap_or("").to_string()); - if maybe_all_proxy.is_none() { - maybe_all_proxy = var_os("ALL_PROXY").map(|ref v| v.to_str().unwrap_or("").to_string()); + } + + #[derive(Clone)] + struct NativeSslStream(Arc>>); + + #[derive(Debug)] + struct NativeSslPoisonError; + + impl ::std::error::Error for NativeSslPoisonError { + fn description(&self) -> &str { "mutex poisoned during TLS operation" } + } + + impl ::std::fmt::Display for NativeSslPoisonError { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { + f.write_str(::std::error::Error::description(self)) } - if let Some(url_value) = match url.scheme() { - "https" => maybe_https_proxy.or(maybe_http_proxy.or(maybe_all_proxy)), - "http" => maybe_http_proxy.or(maybe_all_proxy), - _ => maybe_all_proxy, - } { - if let Ok(proxy_url) = Url::parse(&url_value) { - if let Some(host) = proxy_url.host_str() { - let port = proxy_url.port().unwrap_or(8080); - return Some((host.to_string(), port)); - } - } + } + + impl NativeSslStream { + fn lock<'a>(&'a self) -> IoResult>> { + self.0.lock() + .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) } - None } + impl NetworkStream for NativeSslStream + where T: NetworkStream + { + fn peer_addr(&mut self) -> IoResult { + self.lock() + .and_then(|mut t| t.get_mut().peer_addr()) + } + fn set_read_timeout(&self, dur: Option) -> IoResult<()> { + self.lock() + .and_then(|t| t.get_ref().set_read_timeout(dur)) + } + fn set_write_timeout(&self, dur: Option) -> IoResult<()> { + self.lock() + .and_then(|t| t.get_ref().set_write_timeout(dur)) + } + fn close(&mut self, how: Shutdown) -> IoResult<()> { + self.lock() + .and_then(|mut t| t.get_mut().close(how)) + } + } + + impl Read for NativeSslStream + where T: Read + Write + { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.lock() + .and_then(|mut t| t.read(buf)) + } + } + + impl Write for NativeSslStream + where T: Read + Write + { + fn write(&mut self, buf: &[u8]) -> IoResult { + self.lock() + .and_then(|mut t| t.write(buf)) + } + fn flush(&mut self) -> IoResult<()> { + self.lock() + .and_then(|mut t| t.flush()) + } + } + + // Tell our statically-linked OpenSSL where to find root certs + // cc https://github.com/alexcrichton/git2-rs/blob/master/libgit2-sys/lib.rs#L1267 + #[cfg(not(any(target_os = "windows", target_os = "macos")))] + fn maybe_init_certs() { + use std::sync::{Once, ONCE_INIT}; + static INIT: Once = ONCE_INIT; + INIT.call_once(|| { + openssl_sys::probe::init_ssl_cert_env_vars(); + }); + } + + #[cfg(any(target_os = "windows", target_os = "macos"))] + fn maybe_init_certs() { } +} + +/// Download via hyper; encrypt with rustls +#[cfg(feature = "rustls-backend")] +pub mod rustls { + + extern crate hyper; + extern crate rustls; + + use super::Event; + use std::io; + use std::time::Duration; + use url::Url; + use errors::*; + use hyper_base; + use self::hyper::error::Result as HyperResult; + use self::hyper::net::{SslClient, NetworkStream}; + use std::io::Result as IoResult; + use std::io::{Read, Write}; + use std::net::{SocketAddr, Shutdown}; + use std::sync::{Arc, Mutex, MutexGuard}; + pub fn download(url: &Url, callback: &Fn(Event) -> Result<()>) -> Result<()> { + hyper_base::download::(url, callback) + } - // Short-circuit hyper for the "file:" URL scheme - if try!(download_from_file_url(url, callback)) { - return Ok(()); + struct NativeSslClient; + + impl hyper_base::NewSslClient for NativeSslClient { + fn new() -> Self { NativeSslClient } + fn maybe_init_certs() { } + } + + impl SslClient for NativeSslClient { + type Stream = NativeSslStream; + + fn wrap_client(&self, stream: T, host: &str) -> HyperResult { + let config = global_config(); + let tls_client = rustls::ClientSession::new(&config, host); + + Ok(NativeSslStream(Arc::new(Mutex::new((stream, tls_client))))) } + } - use self::hyper::client::{Client, ProxyConfig}; - use self::hyper::error::Result as HyperResult; - use self::hyper::header::ContentLength; - use self::hyper::net::{SslClient, NetworkStream, HttpsConnector}; - use std::io::Result as IoResult; - use std::io::{Read, Write}; - use std::net::{SocketAddr, Shutdown}; - use std::sync::{Arc, Mutex}; + fn global_config() -> Arc { + use std::fs::File; + use std::io::BufReader; - // The Hyper HTTP client - let client; + lazy_static! { + static ref CONFIG: Arc = init(); + } - let maybe_proxy = proxy_from_env(url); - if url.scheme() == "https" { + fn init() -> Arc { + let mut config = rustls::ClientConfig::new(); + for cert in find_root_cert_paths() { + let certfile = File::open(cert).unwrap(); // FIXME + let mut reader = BufReader::new(certfile); + config.root_store.add_pem_file(&mut reader).unwrap(); // FIXME + } + Arc::new(config) + } - // All the following is adapter code to use native_tls with hyper. + CONFIG.clone() + } - struct NativeSslClient; + fn find_root_cert_paths() -> Vec { + panic!("FIXME: load root certs") + } - impl SslClient for NativeSslClient { - type Stream = NativeSslStream; + #[derive(Clone)] + struct NativeSslStream(Arc>); - fn wrap_client(&self, stream: T, host: &str) -> HyperResult { - use self::native_tls::ClientBuilder as TlsClientBuilder; - use self::hyper::error::Error as HyperError; + #[derive(Debug)] + struct NativeSslPoisonError; - let mut ssl_builder = try!(TlsClientBuilder::new() - .map_err(|e| HyperError::Ssl(Box::new(e)))); - let ssl_stream = try!(ssl_builder.handshake(host, stream) - .map_err(|e| HyperError::Ssl(Box::new(e)))); + impl ::std::error::Error for NativeSslPoisonError { + fn description(&self) -> &str { "mutex poisoned during TLS operation" } + } - Ok(NativeSslStream(Arc::new(Mutex::new(ssl_stream)))) - } - } + impl ::std::fmt::Display for NativeSslPoisonError { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { + f.write_str(::std::error::Error::description(self)) + } + } - #[derive(Clone)] - struct NativeSslStream(Arc>>); + impl NativeSslStream { + fn lock<'a>(&'a self) -> IoResult> { + self.0.lock() + .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) + } + } - #[derive(Debug)] - struct NativeSslPoisonError; + impl NetworkStream for NativeSslStream + where T: NetworkStream + { + fn peer_addr(&mut self) -> IoResult { + self.lock() + .and_then(|mut t| t.0.peer_addr()) + } + fn set_read_timeout(&self, dur: Option) -> IoResult<()> { + self.lock() + .and_then(|t| t.0.set_read_timeout(dur)) + } + fn set_write_timeout(&self, dur: Option) -> IoResult<()> { + self.lock() + .and_then(|t| t.0.set_write_timeout(dur)) + } + fn close(&mut self, how: Shutdown) -> IoResult<()> { + self.lock() + .and_then(|mut t| t.0.close(how)) + } + } - impl ::std::error::Error for NativeSslPoisonError { - fn description(&self) -> &str { "mutex poisoned during TLS operation" } - } + impl Read for NativeSslStream + where T: Read + Write + { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.lock() + .and_then(|mut t| { + let (ref mut stream, ref mut tls) = *t; + while tls.wants_read() { + tls.read_tls(stream).unwrap(); // FIXME + tls.process_new_packets().unwrap(); // FIXME + } - impl ::std::fmt::Display for NativeSslPoisonError { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { - f.write_str(::std::error::Error::description(self)) - } - } + tls.read(buf) + }) + } + } - impl NetworkStream for NativeSslStream - where T: NetworkStream - { - fn peer_addr(&mut self) -> IoResult { - self.0.lock() - .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) - .and_then(|mut t| t.get_mut().peer_addr()) - } - fn set_read_timeout(&self, dur: Option) -> IoResult<()> { - self.0.lock() - .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) - .and_then(|t| t.get_ref().set_read_timeout(dur)) - } - fn set_write_timeout(&self, dur: Option) -> IoResult<()> { - self.0.lock() - .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) - .and_then(|t| t.get_ref().set_write_timeout(dur)) - } - fn close(&mut self, how: Shutdown) -> IoResult<()> { - self.0.lock() - .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) - .and_then(|mut t| t.get_mut().close(how)) - } - } + impl Write for NativeSslStream + where T: Read + Write + { + fn write(&mut self, buf: &[u8]) -> IoResult { + self.lock() + .and_then(|mut t| { + let (ref mut stream, ref mut tls) = *t; - impl Read for NativeSslStream - where T: Read + Write - { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.0.lock() - .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) - .and_then(|mut t| t.read(buf)) - } - } + let res = tls.write(buf); - impl Write for NativeSslStream - where T: Read + Write - { - fn write(&mut self, buf: &[u8]) -> IoResult { - self.0.lock() - .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) - .and_then(|mut t| t.write(buf)) - } - fn flush(&mut self) -> IoResult<()> { - self.0.lock() - .map_err(|_| io::Error::new(io::ErrorKind::Other, NativeSslPoisonError)) - .and_then(|mut t| t.flush()) - } - } + while tls.wants_write() { + tls.write_tls(stream).unwrap(); // FIXME + } + + res + }) + } + fn flush(&mut self) -> IoResult<()> { + self.lock() + .and_then(|mut t| { + t.0.flush() + }) + } + } + +} - maybe_init_certs(); +#[cfg(feature = "hyper")] +pub mod hyper_base { + extern crate hyper; + + use super::Event; + use std::io; + use url::Url; + use errors::*; + use self::hyper::net::{SslClient, HttpStream}; + + pub trait NewSslClient { + fn new() -> Self; + fn maybe_init_certs(); + } + + pub fn download(url: &Url, + callback: &Fn(Event) -> Result<()>) + -> Result<()> + where S: SslClient + NewSslClient + Send + Sync + 'static, + { + + // Short-circuit hyper for the "file:" URL scheme + if try!(download_from_file_url(url, callback)) { + return Ok(()); + } + + use self::hyper::client::{Client, ProxyConfig}; + use self::hyper::header::ContentLength; + use self::hyper::net::{HttpsConnector}; + + // The Hyper HTTP client + let client; + + S::maybe_init_certs(); + + let maybe_proxy = proxy_from_env(url); + if url.scheme() == "https" { if maybe_proxy.is_none() { // Connect with hyper + native_tls - client = Client::with_connector(HttpsConnector::new(NativeSslClient)); + client = Client::with_connector(HttpsConnector::new(S::new())); } else { let proxy_host_port = maybe_proxy.unwrap(); - client = Client::with_proxy_config(ProxyConfig(proxy_host_port.0, proxy_host_port.1, NativeSslClient)); + client = Client::with_proxy_config(ProxyConfig(proxy_host_port.0, proxy_host_port.1, S::new())); } } else if url.scheme() == "http" { if maybe_proxy.is_none() { @@ -319,23 +587,40 @@ pub mod hyper { } } - // Tell our statically-linked OpenSSL where to find root certs - // cc https://github.com/alexcrichton/git2-rs/blob/master/libgit2-sys/lib.rs#L1267 - #[cfg(not(any(target_os = "windows", target_os = "macos")))] - fn maybe_init_certs() { - use std::sync::{Once, ONCE_INIT}; - static INIT: Once = ONCE_INIT; - INIT.call_once(|| { - openssl_sys::probe::init_ssl_cert_env_vars(); - }); - } + fn proxy_from_env(url: &Url) -> Option<(String, u16)> { + use std::env::var_os; - #[cfg(any(target_os = "windows", target_os = "macos"))] - fn maybe_init_certs() { } + let mut maybe_https_proxy = var_os("https_proxy").map(|ref v| v.to_str().unwrap_or("").to_string()); + if maybe_https_proxy.is_none() { + maybe_https_proxy = var_os("HTTPS_PROXY").map(|ref v| v.to_str().unwrap_or("").to_string()); + } + let maybe_http_proxy = var_os("http_proxy").map(|ref v| v.to_str().unwrap_or("").to_string()); + let mut maybe_all_proxy = var_os("all_proxy").map(|ref v| v.to_str().unwrap_or("").to_string()); + if maybe_all_proxy.is_none() { + maybe_all_proxy = var_os("ALL_PROXY").map(|ref v| v.to_str().unwrap_or("").to_string()); + } + if let Some(url_value) = match url.scheme() { + "https" => maybe_https_proxy.or(maybe_http_proxy.or(maybe_all_proxy)), + "http" => maybe_http_proxy.or(maybe_all_proxy), + _ => maybe_all_proxy, + } { + if let Ok(proxy_url) = Url::parse(&url_value) { + if let Some(host) = proxy_url.host_str() { + let port = proxy_url.port().unwrap_or(8080); + return Some((host.to_string(), port)); + } + } + } + None + } fn download_from_file_url(url: &Url, callback: &Fn(Event) -> Result<()>) -> Result { + + use std::fs; + use std::io; + // The file scheme is mostly for use by tests to mock the dist server if url.scheme() == "file" { let src = try!(url.to_file_path() @@ -364,9 +649,10 @@ pub mod hyper { Ok(false) } } + } -#[cfg(not(feature = "curl-mode"))] +#[cfg(not(feature = "curl-backend"))] pub mod curl { use errors::*; @@ -380,7 +666,7 @@ pub mod curl { } } -#[cfg(not(feature = "hyper-mode"))] +#[cfg(not(feature = "hyper-backend"))] pub mod hyper { use errors::*; @@ -393,3 +679,17 @@ pub mod hyper { Err(ErrorKind::BackendUnavailable("hyper").into()) } } + +#[cfg(not(feature = "rustls-backend"))] +pub mod rustls { + + use errors::*; + use url::Url; + use super::Event; + + pub fn download(_url: &Url, + _callback: &Fn(Event) -> Result<()> ) + -> Result<()> { + Err(ErrorKind::BackendUnavailable("rustls").into()) + } +} diff --git a/src/rustup-utils/src/notifications.rs b/src/rustup-utils/src/notifications.rs index 08f4a5016d..ec1d3820a5 100644 --- a/src/rustup-utils/src/notifications.rs +++ b/src/rustup-utils/src/notifications.rs @@ -21,6 +21,7 @@ pub enum Notification<'a> { NoCanonicalPath(&'a Path), UsingCurl, UsingHyper, + UsingRustls, } impl<'a> Notification<'a> { @@ -34,7 +35,7 @@ impl<'a> Notification<'a> { DownloadContentLengthReceived(_) | DownloadDataReceived(_) | DownloadFinished | - UsingCurl | UsingHyper => NotificationLevel::Verbose, + UsingCurl | UsingHyper | UsingRustls => NotificationLevel::Verbose, NoCanonicalPath(_) => NotificationLevel::Warn, } } @@ -58,7 +59,8 @@ impl<'a> Display for Notification<'a> { DownloadFinished => write!(f, "download finished"), NoCanonicalPath(path) => write!(f, "could not canonicalize path: '{}'", path.display()), UsingCurl => write!(f, "downloading with curl"), - UsingHyper => write!(f, "downloading with hyper"), + UsingHyper => write!(f, "downloading with hyper + native_tls"), + UsingRustls => write!(f, "downloading with hyper + rustls"), } } } diff --git a/src/rustup-utils/src/utils.rs b/src/rustup-utils/src/utils.rs index dbb46dccb1..8df0b1bd14 100644 --- a/src/rustup-utils/src/utils.rs +++ b/src/rustup-utils/src/utils.rs @@ -176,24 +176,18 @@ fn download_file_(url: &Url, use sha2::Digest; use std::cell::RefCell; - use download::{self, Event, hyper, curl}; + use download::download_to_path_with_backend; + use download::{self, Event, Backend}; notify_handler(Notification::DownloadingFile(url, path)); - let file = RefCell::new(try!(fs::File::create(&path).chain_err( - || "error creating file for download"))); let hasher = RefCell::new(hasher); // This callback will write the download to disk and optionally // hash the contents, then forward the notification up the stack let callback: &Fn(Event) -> download::Result<()> = &|msg| { - use download::ChainErr; - match msg { Event::DownloadDataReceived(data) => { - try!(ChainErr::chain_err( - io::Write::write_all(&mut *file.borrow_mut(), data), - || "unable to write download to disk")); if let Some(ref mut h) = *hasher.borrow_mut() { h.input(data); } @@ -214,15 +208,17 @@ fn download_file_(url: &Url, }; // Download the file - if env::var_os("RUSTUP_USE_HYPER").is_some() { - notify_handler(Notification::UsingHyper); - try!(hyper::download(url, callback)); + let use_hyper_backend = env::var_os("RUSTUP_USE_HYPER").is_some(); + let use_rustls_backend = env::var_os("RUSTUP_USE_RUSTLS").is_some(); + let (backend, notification) = if use_hyper_backend { + (Backend::Hyper, Notification::UsingHyper) + } else if use_rustls_backend { + (Backend::Rustls, Notification::UsingRustls) } else { - notify_handler(Notification::UsingCurl); - try!(curl::download(url, callback)); - } - - try!(file.borrow_mut().sync_data().chain_err(|| "unable to sync download to disk")); + (Backend::Curl, Notification::UsingCurl) + }; + notify_handler(notification); + try!(download_to_path_with_backend(backend, url, path, Some(callback))); notify_handler(Notification::DownloadFinished);