diff options
Diffstat (limited to 'extra/git2-curl/src/lib.rs')
-rw-r--r-- | extra/git2-curl/src/lib.rs | 291 |
1 files changed, 0 insertions, 291 deletions
diff --git a/extra/git2-curl/src/lib.rs b/extra/git2-curl/src/lib.rs deleted file mode 100644 index dbdbdc4a6..000000000 --- a/extra/git2-curl/src/lib.rs +++ /dev/null @@ -1,291 +0,0 @@ -//! A crate for using libcurl as a backend for HTTP git requests with git2-rs. -//! -//! This crate provides one public function, `register`, which will register -//! a custom HTTP transport with libcurl for any HTTP requests made by libgit2. -//! At this time the `register` function is unsafe for the same reasons that -//! `git2::transport::register` is also unsafe. -//! -//! It is not recommended to use this crate wherever possible. The current -//! libcurl backend used, `curl-rust`, only supports executing a request in one -//! method call implying no streaming support. This consequently means that -//! when a repository is cloned the entire contents of the repo are downloaded -//! into memory, and *then* written off to disk by libgit2 afterwards. It -//! should be possible to alleviate this problem in the future. -//! -//! > **NOTE**: At this time this crate likely does not support a `git push` -//! > operation, only clones. - -#![doc(html_root_url = "https://docs.rs/git2-curl/0.19")] -#![deny(missing_docs)] -#![warn(rust_2018_idioms)] -#![cfg_attr(test, deny(warnings))] - -use std::error; -use std::io::prelude::*; -use std::io::{self, Cursor}; -use std::str; -use std::sync::{Arc, Mutex, Once}; - -use curl::easy::{Easy, List}; -use git2::transport::SmartSubtransportStream; -use git2::transport::{Service, SmartSubtransport, Transport}; -use git2::Error; -use log::{debug, info}; -use url::Url; - -struct CurlTransport { - handle: Arc<Mutex<Easy>>, - /// The URL of the remote server, e.g. "https://github.com/user/repo" - /// - /// This is an empty string until the first action is performed. - /// If there is an HTTP redirect, this will be updated with the new URL. - base_url: Arc<Mutex<String>>, -} - -struct CurlSubtransport { - handle: Arc<Mutex<Easy>>, - service: &'static str, - url_path: &'static str, - base_url: Arc<Mutex<String>>, - method: &'static str, - reader: Option<Cursor<Vec<u8>>>, - sent_request: bool, -} - -/// Register the libcurl backend for HTTP requests made by libgit2. -/// -/// This function takes one parameter, a `handle`, which is used to perform all -/// future HTTP requests. The handle can be previously configured with -/// information such as proxies, SSL information, etc. -/// -/// This function is unsafe largely for the same reasons as -/// `git2::transport::register`: -/// -/// * The function needs to be synchronized against all other creations of -/// transport (any API calls to libgit2). -/// * The function will leak `handle` as once registered it is not currently -/// possible to unregister the backend. -/// -/// This function may be called concurrently, but only the first `handle` will -/// be used. All others will be discarded. -pub unsafe fn register(handle: Easy) { - static INIT: Once = Once::new(); - - let handle = Arc::new(Mutex::new(handle)); - let handle2 = handle.clone(); - INIT.call_once(move || { - git2::transport::register("http", move |remote| factory(remote, handle.clone())).unwrap(); - git2::transport::register("https", move |remote| factory(remote, handle2.clone())).unwrap(); - }); -} - -fn factory(remote: &git2::Remote<'_>, handle: Arc<Mutex<Easy>>) -> Result<Transport, Error> { - Transport::smart( - remote, - true, - CurlTransport { - handle: handle, - base_url: Arc::new(Mutex::new(String::new())), - }, - ) -} - -impl SmartSubtransport for CurlTransport { - fn action( - &self, - url: &str, - action: Service, - ) -> Result<Box<dyn SmartSubtransportStream>, Error> { - let mut base_url = self.base_url.lock().unwrap(); - if base_url.len() == 0 { - *base_url = url.to_string(); - } - let (service, path, method) = match action { - Service::UploadPackLs => ("upload-pack", "/info/refs?service=git-upload-pack", "GET"), - Service::UploadPack => ("upload-pack", "/git-upload-pack", "POST"), - Service::ReceivePackLs => { - ("receive-pack", "/info/refs?service=git-receive-pack", "GET") - } - Service::ReceivePack => ("receive-pack", "/git-receive-pack", "POST"), - }; - info!("action {} {}", service, path); - Ok(Box::new(CurlSubtransport { - handle: self.handle.clone(), - service: service, - url_path: path, - base_url: self.base_url.clone(), - method: method, - reader: None, - sent_request: false, - })) - } - - fn close(&self) -> Result<(), Error> { - Ok(()) // ... - } -} - -impl CurlSubtransport { - fn err<E: Into<Box<dyn error::Error + Send + Sync>>>(&self, err: E) -> io::Error { - io::Error::new(io::ErrorKind::Other, err) - } - - fn execute(&mut self, data: &[u8]) -> io::Result<()> { - if self.sent_request { - return Err(self.err("already sent HTTP request")); - } - let agent = format!("git/1.0 (git2-curl {})", env!("CARGO_PKG_VERSION")); - - // Parse our input URL to figure out the host - let url = format!("{}{}", self.base_url.lock().unwrap(), self.url_path); - let parsed = Url::parse(&url).map_err(|_| self.err("invalid url, failed to parse"))?; - let host = match parsed.host_str() { - Some(host) => host, - None => return Err(self.err("invalid url, did not have a host")), - }; - - // Prep the request - debug!("request to {}", url); - let mut h = self.handle.lock().unwrap(); - h.url(&url)?; - h.useragent(&agent)?; - h.follow_location(true)?; - match self.method { - "GET" => h.get(true)?, - "PUT" => h.put(true)?, - "POST" => h.post(true)?, - other => h.custom_request(other)?, - } - - let mut headers = List::new(); - headers.append(&format!("Host: {}", host))?; - if data.len() > 0 { - h.post_fields_copy(data)?; - headers.append(&format!( - "Accept: application/x-git-{}-result", - self.service - ))?; - headers.append(&format!( - "Content-Type: \ - application/x-git-{}-request", - self.service - ))?; - } else { - headers.append("Accept: */*")?; - } - headers.append("Expect:")?; - h.http_headers(headers)?; - - let mut content_type = None; - let mut data = Vec::new(); - { - let mut h = h.transfer(); - - // Look for the Content-Type header - h.header_function(|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_type = Some(value.trim().to_string()); - } - - true - })?; - - // Collect the request's response in-memory - h.write_function(|buf| { - data.extend_from_slice(buf); - Ok(buf.len()) - })?; - - // Send the request - h.perform()?; - } - - let code = h.response_code()?; - if code != 200 { - return Err(self.err( - &format!( - "failed to receive HTTP 200 response: \ - got {}", - code - )[..], - )); - } - - // Check returned headers - let expected = match self.method { - "GET" => format!("application/x-git-{}-advertisement", self.service), - _ => format!("application/x-git-{}-result", self.service), - }; - match content_type { - 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(data); - self.reader = Some(rdr); - - // If there was a redirect, update the `CurlTransport` with the new base. - if let Ok(Some(effective_url)) = h.effective_url() { - let new_base = if effective_url.ends_with(self.url_path) { - // Strip the action from the end. - &effective_url[..effective_url.len() - self.url_path.len()] - } else { - // I'm not sure if this code path makes sense, but it's what - // libgit does. - effective_url - }; - *self.base_url.lock().unwrap() = new_base.to_string(); - } - - Ok(()) - } -} - -impl Read for CurlSubtransport { - fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - if self.reader.is_none() { - self.execute(&[])?; - } - self.reader.as_mut().unwrap().read(buf) - } -} - -impl Write for CurlSubtransport { - fn write(&mut self, data: &[u8]) -> io::Result<usize> { - if self.reader.is_none() { - self.execute(data)?; - } - Ok(data.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} |