diff --git a/git2-curl/Cargo.toml b/git2-curl/Cargo.toml index a483462a8a..58e0fea374 100644 --- a/git2-curl/Cargo.toml +++ b/git2-curl/Cargo.toml @@ -14,7 +14,7 @@ Intended to be used with the git2 crate. """ [dependencies] -curl = "0.2" +curl = { git = "https://github.com/alexcrichton/curl-rust", branch = "rewrite" } url = "1.0" log = "0.3" git2 = { path = "..", version = "0.4" } diff --git a/git2-curl/src/lib.rs b/git2-curl/src/lib.rs index 22e859ba4c..c124d079cf 100644 --- a/git2-curl/src/lib.rs +++ b/git2-curl/src/lib.rs @@ -22,35 +22,34 @@ extern crate curl; extern crate url; #[macro_use] extern crate log; +use std::ascii::AsciiExt; +use std::error; use std::io::prelude::*; use std::io::{self, Cursor}; +use std::mem; +use std::str; use std::sync::{Once, ONCE_INIT, Arc, Mutex}; -use std::error; -use curl::http::handle::Method; -use curl::http::{Handle, Request}; +use curl::easy::{Easy, List}; use git2::Error; use git2::transport::{SmartSubtransportStream}; use git2::transport::{Transport, SmartSubtransport, Service}; use url::Url; struct CurlTransport { - handle: Arc>, + handle: Arc>>, } struct CurlSubtransport { - handle: Arc>, + handle: Arc>>, service: &'static str, url_path: &'static str, base_url: String, - method: Method, + method: &'static str, reader: Option>>, sent_request: bool, } -struct MyHandle(Handle); -unsafe impl Send for MyHandle {} // Handle is not send... - /// Register the libcurl backend for HTTP requests made by libgit2. /// /// This function takes one parameter, a `handle`, which is used to perform all @@ -67,10 +66,10 @@ unsafe impl Send for MyHandle {} // Handle is not send... /// /// This function may be called concurrently, but only the first `handle` will /// be used. All others will be discarded. -pub unsafe fn register(handle: Handle) { +pub unsafe fn register(handle: Easy<'static>) { static INIT: Once = ONCE_INIT; - let handle = Arc::new(Mutex::new(MyHandle(handle))); + let handle = Arc::new(Mutex::new(handle)); let handle2 = handle.clone(); INIT.call_once(move || { git2::transport::register("http", move |remote| { @@ -82,7 +81,7 @@ pub unsafe fn register(handle: Handle) { }); } -fn factory(remote: &git2::Remote, handle: Arc>) +fn factory(remote: &git2::Remote, handle: Arc>>) -> Result { Transport::smart(remote, true, CurlTransport { handle: handle }) } @@ -92,17 +91,16 @@ impl SmartSubtransport for CurlTransport { -> Result, Error> { let (service, path, method) = match action { Service::UploadPackLs => { - ("upload-pack", "/info/refs?service=git-upload-pack", Method::Get) + ("upload-pack", "/info/refs?service=git-upload-pack", "GET") } Service::UploadPack => { - ("upload-pack", "/git-upload-pack", Method::Post) + ("upload-pack", "/git-upload-pack", "POST") } Service::ReceivePackLs => { - ("receive-pack", "/info/refs?service=git-receive-pack", - Method::Get) + ("receive-pack", "/info/refs?service=git-receive-pack", "GET") } Service::ReceivePack => { - ("receive-pack", "/git-receive-pack", Method::Post) + ("receive-pack", "/git-receive-pack", "POST") } }; info!("action {} {}", service, path); @@ -127,7 +125,7 @@ impl CurlSubtransport { io::Error::new(io::ErrorKind::Other, err) } - fn execute(&mut self, mut data: &[u8]) -> io::Result<()> { + fn execute(&mut self, data: &[u8]) -> io::Result<()> { if self.sent_request { return Err(self.err("already sent HTTP request")) } @@ -146,46 +144,90 @@ impl CurlSubtransport { // Prep the request debug!("request to {}", url); let mut h = self.handle.lock().unwrap(); - let mut req = Request::new(&mut h.0, self.method) - .uri(url) - .header("User-Agent", &agent) - .header("Host", host) - .follow_redirects(true); + try!(h.url(&url)); + try!(h.useragent(&agent)); + try!(h.follow_location(true)); + match self.method { + "GET" => try!(h.get(true)), + "PUT" => try!(h.put(true)), + "POST" => try!(h.post(true)), + other => try!(h.custom_request(other)), + } + + let mut headers = List::new(); + try!(headers.append(&format!("Host: {}", host))); if data.len() > 0 { - req = req.body(&mut data) - .content_length(data.len()) - .header("Accept", &format!("application/x-git-{}-result", - self.service)) - .header("Content-Type", - &format!("application/x-git-{}-request", - self.service)); + try!(h.post_fields_copy(data)); + try!(headers.append(&format!("Accept: application/x-git-{}-result", + self.service))); + try!(headers.append(&format!("Content-Type: \ + application/x-git-{}-request", + self.service))); } else { - req = req.header("Accept", "*/*"); + try!(headers.append("Accept: */*")); } + try!(headers.append("Expect:")); + try!(h.http_headers(headers)); + + // Look for the Content-Type header + let content_type = Arc::new(Mutex::new(None)); + let content_type2 = content_type.clone(); + try!(h.header_function(move |header| { + let header = match str::from_utf8(header) { + Ok(s) => s, + Err(..) => return true, + }; + let mut parts = header.splitn(2, ": "); + let name = parts.next().unwrap(); + let value = match parts.next() { + Some(value) => value, + None => return true, + }; + if name.eq_ignore_ascii_case("Content-Type") { + *content_type2.lock().unwrap() = Some(value.trim().to_string()); + } + + true + })); + + // Collect the request's response in-memory + let data = Arc::new(Mutex::new(Vec::new())); + let data2 = data.clone(); + try!(h.write_function(move |data| { + data2.lock().unwrap().extend_from_slice(data); + data.len() + })); // Send the request - let resp = try!(req.exec().map_err(|e| self.err(e))); - debug!("response: {}", resp); - if resp.get_code() != 200 { + try!(h.perform()); + let code = try!(h.response_code()); + if code != 200 { return Err(self.err(&format!("failed to receive HTTP 200 response: \ - got {}", resp.get_code())[..])) + got {}", code)[..])) } // Check returned headers let expected = match self.method { - Method::Get => format!("application/x-git-{}-advertisement", - self.service), + "GET" => format!("application/x-git-{}-advertisement", + self.service), _ => format!("application/x-git-{}-result", self.service), }; - if &resp.get_header("content-type") != &[expected.clone()] { - return Err(self.err(&format!("invalid Content-Type header: \ - found `{:?}` expected `{}`", - resp.get_header("Content-Type"), - expected)[..])) + match *content_type.lock().unwrap() { + Some(ref content_type) if *content_type != expected => { + return Err(self.err(&format!("expected a Content-Type header \ + with `{}` but found `{}`", + expected, content_type)[..])) + } + Some(..) => {} + None => { + return Err(self.err(&format!("expected a Content-Type header \ + with `{}` but didn't find one", + expected)[..])) + } } // Ok, time to read off some data. - let rdr = Cursor::new(resp.move_body()); + let rdr = Cursor::new(mem::replace(&mut *data.lock().unwrap(), Vec::new())); self.reader = Some(rdr); Ok(()) } diff --git a/git2-curl/tests/all.rs b/git2-curl/tests/all.rs index 2249425ca6..7dbc5a8e17 100644 --- a/git2-curl/tests/all.rs +++ b/git2-curl/tests/all.rs @@ -15,8 +15,7 @@ const PORT: u16 = 7848; fn main() { unsafe { - let h = curl::http::handle::Handle::new(); - git2_curl::register(h.timeout(1000)); + git2_curl::register(curl::easy::Easy::new()); } // Spin up a server for git-http-backend