diff --git a/Cargo.lock b/Cargo.lock index aab3cb77dd6..860297bbd9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,15 +6,14 @@ dependencies = [ "bufstream 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "crates-io 0.2.0", "crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "curl 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "curl-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "curl 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.78 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "fs2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "git2-curl 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "git2-curl 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "hamcrest 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -72,7 +71,7 @@ dependencies = [ name = "crates-io" version = "0.2.0" dependencies = [ - "curl 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "curl 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -84,19 +83,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "curl" -version = "0.2.19" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "curl-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "curl-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "curl-sys" -version = "0.1.34" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)", @@ -178,10 +175,10 @@ dependencies = [ [[package]] name = "git2-curl" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "curl 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "curl 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 6560c9ee320..2b0c4acbb26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,19 +20,18 @@ path = "src/cargo/lib.rs" advapi32-sys = "0.1" crates-io = { path = "src/crates-io", version = "0.2" } crossbeam = "0.2" -curl = "0.2" -curl-sys = "0.1" +curl = "0.3" docopt = "0.6" env_logger = "0.3" filetime = "0.1" flate2 = "0.2" fs2 = "0.2" git2 = "0.4" -git2-curl = "0.4" +libgit2-sys = "0.4" +git2-curl = "0.5" glob = "0.2" kernel32-sys = "0.2" libc = "0.2" -libgit2-sys = "0.4" log = "0.3" num_cpus = "0.2" regex = "0.1" diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index c20b150dfcd..45676e2ef99 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -6,7 +6,6 @@ extern crate crates_io as registry; extern crate crossbeam; extern crate curl; -extern crate curl_sys; extern crate docopt; extern crate filetime; extern crate flate2; diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 4b4358e12b1..49764c67482 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -3,8 +3,9 @@ use std::env; use std::fs::{self, File}; use std::iter::repeat; use std::path::{Path, PathBuf}; +use std::time::Duration; -use curl::http; +use curl::easy::Easy; use git2; use registry::{Registry, NewCrate, NewCrateDependency}; use term::color::BLACK; @@ -156,24 +157,22 @@ pub fn registry(config: &Config, } /// Create a new HTTP handle with appropriate global configuration for cargo. -pub fn http_handle(config: &Config) -> CargoResult { +pub fn http_handle(config: &Config) -> CargoResult { // The timeout option for libcurl by default times out the entire transfer, // but we probably don't want this. Instead we only set timeouts for the // connect phase as well as a "low speed" timeout so if we don't receive // many bytes in a large-ish period of time then we time out. - let handle = http::handle().timeout(0) - .connect_timeout(30_000 /* milliseconds */) - .low_speed_limit(10 /* bytes per second */) - .low_speed_timeout(30 /* seconds */); - let handle = match try!(http_proxy(config)) { - Some(proxy) => handle.proxy(proxy), - None => handle, - }; - let handle = match try!(http_timeout(config)) { - Some(timeout) => handle.connect_timeout(timeout as usize) - .low_speed_timeout((timeout as usize) / 1000), - None => handle, - }; + let mut handle = Easy::new(); + try!(handle.connect_timeout(Duration::new(30, 0))); + try!(handle.low_speed_limit(10 /* bytes per second */)); + try!(handle.low_speed_time(Duration::new(30, 0))); + if let Some(proxy) = try!(http_proxy(config)) { + try!(handle.proxy(&proxy)); + } + if let Some(timeout) = try!(http_timeout(config)) { + try!(handle.connect_timeout(Duration::new(timeout as u64, 0))); + try!(handle.low_speed_time(Duration::new(timeout as u64, 0))); + } Ok(handle) } diff --git a/src/cargo/sources/registry.rs b/src/cargo/sources/registry.rs index 614d6540a60..06e6344bb96 100644 --- a/src/cargo/sources/registry.rs +++ b/src/cargo/sources/registry.rs @@ -164,7 +164,7 @@ use std::io::SeekFrom; use std::io::prelude::*; use std::path::{PathBuf, Path}; -use curl::http; +use curl::easy::Easy; use flate2::read::GzDecoder; use git2; use rustc_serialize::hex::ToHex; @@ -188,7 +188,7 @@ pub struct RegistrySource<'cfg> { cache_path: Filesystem, src_path: Filesystem, config: &'cfg Config, - handle: Option, + handle: Option, hashes: HashMap<(String, String), String>, // (name, vers) => cksum cache: HashMap>, updated: bool, @@ -300,24 +300,34 @@ impl<'cfg> RegistrySource<'cfg> { self.handle.as_mut().unwrap() } }; - // TODO: don't download into memory (curl-rust doesn't expose it) - let resp = try!(handle.get(url.to_string()).follow_redirects(true).exec()); - if resp.get_code() != 200 && resp.get_code() != 0 { - return Err(internal(format!("failed to get 200 response from {}\n{}", - url, resp))) + // 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 + try!(handle.get(true)); + try!(handle.url(&url.to_string())); + try!(handle.follow_location(true)); + let mut state = Sha256::new(); + let mut body = Vec::new(); + { + let mut handle = handle.transfer(); + try!(handle.write_function(|buf| { + state.update(buf); + body.extend_from_slice(buf); + Ok(buf.len()) + })); + try!(handle.perform()); + } + let code = try!(handle.response_code()); + if code != 200 && code != 0 { + bail!("failed to get 200 response from `{}`, got {}", url, code) } // Verify what we just downloaded - let actual = { - let mut state = Sha256::new(); - state.update(resp.get_body()); - state.finish() - }; - if actual.to_hex() != expected_hash { + if state.finish().to_hex() != expected_hash { bail!("failed to verify the checksum of `{}`", pkg) } - try!(dst.write_all(resp.get_body())); + try!(dst.write_all(&body)); try!(dst.seek(SeekFrom::Start(0))); Ok(dst) } diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index b7c03c21045..a31fffc04c1 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -8,7 +8,6 @@ use std::str; use std::string; use curl; -use curl_sys; use git2; use rustc_serialize::json; use semver; @@ -302,17 +301,13 @@ impl NetworkError for git2::Error { } } } -impl NetworkError for curl::ErrCode { +impl NetworkError for curl::Error { fn maybe_spurious(&self) -> bool { - match self.code() { - curl_sys::CURLcode::CURLE_COULDNT_CONNECT | - curl_sys::CURLcode::CURLE_COULDNT_RESOLVE_PROXY | - curl_sys::CURLcode::CURLE_COULDNT_RESOLVE_HOST | - curl_sys::CURLcode::CURLE_OPERATION_TIMEDOUT | - curl_sys::CURLcode::CURLE_RECV_ERROR - => true, - _ => false - } + self.is_couldnt_connect() || + self.is_couldnt_resolve_proxy() || + self.is_couldnt_resolve_host() || + self.is_operation_timedout() || + self.is_recv_error() } } @@ -334,7 +329,7 @@ from_error! { git2::Error, json::DecoderError, json::EncoderError, - curl::ErrCode, + curl::Error, CliError, toml::Error, url::ParseError, @@ -360,7 +355,7 @@ impl CargoError for io::Error {} impl CargoError for git2::Error {} impl CargoError for json::DecoderError {} impl CargoError for json::EncoderError {} -impl CargoError for curl::ErrCode {} +impl CargoError for curl::Error {} impl CargoError for ProcessError {} impl CargoError for CargoTestError {} impl CargoError for CliError {} diff --git a/src/crates-io/Cargo.toml b/src/crates-io/Cargo.toml index 72f73229579..951523617c9 100644 --- a/src/crates-io/Cargo.toml +++ b/src/crates-io/Cargo.toml @@ -13,6 +13,6 @@ name = "crates_io" path = "lib.rs" [dependencies] -curl = "0.2" +curl = "0.3" url = "1.0" rustc-serialize = "0.3" diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs index 10ab68a728f..8ecb932fbde 100644 --- a/src/crates-io/lib.rs +++ b/src/crates-io/lib.rs @@ -9,9 +9,7 @@ use std::io::prelude::*; use std::io::{self, Cursor}; use std::result; -use curl::http; -use curl::http::handle::Method::{Put, Get, Delete}; -use curl::http::handle::{Method, Request}; +use curl::easy::{Easy, List}; use rustc_serialize::json; use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET}; @@ -19,7 +17,7 @@ use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET}; pub struct Registry { host: String, token: Option, - handle: http::Handle, + handle: Easy, } pub type Result = result::Result; @@ -31,8 +29,8 @@ pub enum Auth { } pub enum Error { - Curl(curl::ErrCode), - NotOkResponse(http::Response), + Curl(curl::Error), + NotOkResponse(u32, Vec, Vec), NonUtf8Body, Api(Vec), Unauthorized, @@ -55,6 +53,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: curl::Error) -> Error { + Error::Curl(err) + } +} + #[derive(RustcDecodable)] pub struct Crate { pub name: String, @@ -109,11 +113,12 @@ pub struct User { impl Registry { pub fn new(host: String, token: Option) -> Registry { - Registry::new_handle(host, token, http::Handle::new()) + Registry::new_handle(host, token, Easy::new()) } - pub fn new_handle(host: String, token: Option, - handle: http::Handle) -> Registry { + pub fn new_handle(host: String, + token: Option, + handle: Easy) -> Registry { Registry { host: host, token: token, @@ -177,12 +182,17 @@ impl Registry { Some(s) => s, None => return Err(Error::TokenMissing), }; - let request = self.handle.put(url, &mut body) - .content_length(size) - .header("Accept", "application/json") - .header("Authorization", &token); - let response = handle(request.exec()); - let _body = try!(response); + try!(self.handle.put(true)); + try!(self.handle.url(&url)); + try!(self.handle.in_filesize(size as u64)); + let mut headers = List::new(); + try!(headers.append("Accept: application/json")); + try!(headers.append(&format!("Authorization: {}", token))); + try!(self.handle.http_headers(headers)); + + let _body = try!(handle(&mut self.handle, &mut |buf| { + body.read(buf).unwrap_or(0) + })); Ok(()) } @@ -190,7 +200,7 @@ impl Registry { let formated_query = percent_encode(query.as_bytes(), QUERY_ENCODE_SET); let body = try!(self.req( format!("/crates?q={}&per_page={}", formated_query, limit), - None, Get, Auth::Unauthorized + None, Auth::Unauthorized )); let crates = try!(json::decode::(&body)); @@ -212,51 +222,75 @@ impl Registry { } fn put(&mut self, path: String, b: &[u8]) -> Result { - self.req(path, Some(b), Put, Auth::Authorized) + try!(self.handle.put(true)); + self.req(path, Some(b), Auth::Authorized) } fn get(&mut self, path: String) -> Result { - self.req(path, None, Get, Auth::Authorized) + try!(self.handle.get(true)); + self.req(path, None, Auth::Authorized) } fn delete(&mut self, path: String, b: Option<&[u8]>) -> Result { - self.req(path, b, Delete, Auth::Authorized) + try!(self.handle.custom_request("DELETE")); + self.req(path, b, Auth::Authorized) } - fn req(&mut self, path: String, body: Option<&[u8]>, - method: Method, authorized: Auth) -> Result { - let mut req = Request::new(&mut self.handle, method) - .uri(format!("{}/api/v1{}", self.host, path)) - .header("Accept", "application/json") - .content_type("application/json"); + fn req(&mut self, + path: String, + body: Option<&[u8]>, + authorized: Auth) -> Result { + try!(self.handle.url(&format!("{}/api/v1{}", self.host, path))); + let mut headers = List::new(); + try!(headers.append("Accept: application/json")); + try!(headers.append("Content-Type: application/json")); if authorized == Auth::Authorized { let token = match self.token.as_ref() { Some(s) => s, None => return Err(Error::TokenMissing), }; - req = req.header("Authorization", &token); + try!(headers.append(&format!("Authorization: {}", token))); } + try!(self.handle.http_headers(headers)); match body { - Some(b) => req = req.body(b), - None => {} + Some(mut body) => { + try!(self.handle.upload(true)); + try!(self.handle.in_filesize(body.len() as u64)); + handle(&mut self.handle, &mut |buf| body.read(buf).unwrap_or(0)) + } + None => handle(&mut self.handle, &mut |_| 0), } - handle(req.exec()) } } -fn handle(response: result::Result) - -> Result { - let response = try!(response.map_err(Error::Curl)); - match response.get_code() { +fn handle(handle: &mut Easy, + read: &mut FnMut(&mut [u8]) -> usize) -> Result { + let mut headers = Vec::new(); + let mut body = Vec::new(); + { + let mut handle = handle.transfer(); + try!(handle.read_function(|buf| Ok(read(buf)))); + try!(handle.write_function(|data| { + body.extend_from_slice(data); + Ok(data.len()) + })); + try!(handle.header_function(|data| { + headers.push(String::from_utf8_lossy(data).into_owned()); + true + })); + try!(handle.perform()); + } + + match try!(handle.response_code()) { 0 => {} // file upload url sometimes 200 => {} 403 => return Err(Error::Unauthorized), 404 => return Err(Error::NotFound), - _ => return Err(Error::NotOkResponse(response)) + code => return Err(Error::NotOkResponse(code, headers, body)) } - let body = match String::from_utf8(response.move_body()) { + let body = match String::from_utf8(body) { Ok(body) => body, Err(..) => return Err(Error::NonUtf8Body), }; @@ -275,8 +309,15 @@ impl fmt::Display for Error { match *self { Error::NonUtf8Body => write!(f, "response body was not utf-8"), Error::Curl(ref err) => write!(f, "http error: {}", err), - Error::NotOkResponse(ref resp) => { - write!(f, "failed to get a 200 OK response: {}", resp) + Error::NotOkResponse(code, ref headers, ref body) => { + try!(writeln!(f, "failed to get a 200 OK response, got {}", code)); + try!(writeln!(f, "headers:")); + for header in headers { + try!(writeln!(f, " {}", header)); + } + try!(writeln!(f, "body:")); + try!(writeln!(f, "{}", String::from_utf8_lossy(body))); + Ok(()) } Error::Api(ref errs) => { write!(f, "api errors: {}", errs.join(", "))