1#![doc(html_root_url = "https://docs.rs/git2-curl/0.21")]
19#![deny(missing_docs)]
20#![warn(rust_2018_idioms)]
21#![cfg_attr(test, deny(warnings))]
22
23use std::error;
24use std::io::prelude::*;
25use std::io::{self, Cursor};
26use std::str;
27use std::sync::{Arc, Mutex, Once};
28
29use curl::easy::{Easy, List};
30use git2::transport::SmartSubtransportStream;
31use git2::transport::{Service, SmartSubtransport, Transport};
32use git2::Error;
33use log::{debug, info};
34use url::Url;
35
36struct CurlTransport {
37 handle: Arc<Mutex<Easy>>,
38 base_url: Arc<Mutex<String>>,
43}
44
45struct CurlSubtransport {
46 handle: Arc<Mutex<Easy>>,
47 service: &'static str,
48 url_path: &'static str,
49 base_url: Arc<Mutex<String>>,
50 method: &'static str,
51 reader: Option<Cursor<Vec<u8>>>,
52 sent_request: bool,
53}
54
55pub unsafe fn register(handle: Easy) {
72 static INIT: Once = Once::new();
73
74 let handle = Arc::new(Mutex::new(handle));
75 let handle2 = handle.clone();
76 INIT.call_once(move || {
77 git2::transport::register("http", move |remote| factory(remote, handle.clone())).unwrap();
78 git2::transport::register("https", move |remote| factory(remote, handle2.clone())).unwrap();
79 });
80}
81
82fn factory(remote: &git2::Remote<'_>, handle: Arc<Mutex<Easy>>) -> Result<Transport, Error> {
83 Transport::smart(
84 remote,
85 true,
86 CurlTransport {
87 handle: handle,
88 base_url: Arc::new(Mutex::new(String::new())),
89 },
90 )
91}
92
93impl SmartSubtransport for CurlTransport {
94 fn action(
95 &self,
96 url: &str,
97 action: Service,
98 ) -> Result<Box<dyn SmartSubtransportStream>, Error> {
99 let mut base_url = self.base_url.lock().unwrap();
100 if base_url.len() == 0 {
101 *base_url = url.to_string();
102 }
103 let (service, path, method) = match action {
104 Service::UploadPackLs => ("upload-pack", "/info/refs?service=git-upload-pack", "GET"),
105 Service::UploadPack => ("upload-pack", "/git-upload-pack", "POST"),
106 Service::ReceivePackLs => {
107 ("receive-pack", "/info/refs?service=git-receive-pack", "GET")
108 }
109 Service::ReceivePack => ("receive-pack", "/git-receive-pack", "POST"),
110 };
111 info!("action {} {}", service, path);
112 Ok(Box::new(CurlSubtransport {
113 handle: self.handle.clone(),
114 service: service,
115 url_path: path,
116 base_url: self.base_url.clone(),
117 method: method,
118 reader: None,
119 sent_request: false,
120 }))
121 }
122
123 fn close(&self) -> Result<(), Error> {
124 Ok(()) }
126}
127
128impl CurlSubtransport {
129 fn err<E: Into<Box<dyn error::Error + Send + Sync>>>(&self, err: E) -> io::Error {
130 io::Error::new(io::ErrorKind::Other, err)
131 }
132
133 fn execute(&mut self, data: &[u8]) -> io::Result<()> {
134 if self.sent_request {
135 return Err(self.err("already sent HTTP request"));
136 }
137 let agent = format!("git/1.0 (git2-curl {})", env!("CARGO_PKG_VERSION"));
138
139 let url = format!("{}{}", self.base_url.lock().unwrap(), self.url_path);
141 let parsed = Url::parse(&url).map_err(|_| self.err("invalid url, failed to parse"))?;
142 let host = match parsed.host_str() {
143 Some(host) => host,
144 None => return Err(self.err("invalid url, did not have a host")),
145 };
146
147 debug!("request to {}", url);
149 let mut h = self.handle.lock().unwrap();
150 h.url(&url)?;
151 h.useragent(&agent)?;
152 h.follow_location(true)?;
153 match self.method {
154 "GET" => h.get(true)?,
155 "PUT" => h.put(true)?,
156 "POST" => h.post(true)?,
157 other => h.custom_request(other)?,
158 }
159
160 let mut headers = List::new();
161 headers.append(&format!("Host: {}", host))?;
162 if data.len() > 0 {
163 h.post_fields_copy(data)?;
164 headers.append(&format!(
165 "Accept: application/x-git-{}-result",
166 self.service
167 ))?;
168 headers.append(&format!(
169 "Content-Type: \
170 application/x-git-{}-request",
171 self.service
172 ))?;
173 } else {
174 headers.append("Accept: */*")?;
175 }
176 headers.append("Expect:")?;
177 h.http_headers(headers)?;
178
179 let mut content_type = None;
180 let mut data = Vec::new();
181 {
182 let mut h = h.transfer();
183
184 h.header_function(|header| {
186 let header = match str::from_utf8(header) {
187 Ok(s) => s,
188 Err(..) => return true,
189 };
190 let mut parts = header.splitn(2, ": ");
191 let name = parts.next().unwrap();
192 let value = match parts.next() {
193 Some(value) => value,
194 None => return true,
195 };
196 if name.eq_ignore_ascii_case("Content-Type") {
197 content_type = Some(value.trim().to_string());
198 }
199
200 true
201 })?;
202
203 h.write_function(|buf| {
205 data.extend_from_slice(buf);
206 Ok(buf.len())
207 })?;
208
209 h.perform()?;
211 }
212
213 let code = h.response_code()?;
214 if code != 200 {
215 return Err(self.err(
216 &format!(
217 "failed to receive HTTP 200 response: \
218 got {}",
219 code
220 )[..],
221 ));
222 }
223
224 let expected = match self.method {
226 "GET" => format!("application/x-git-{}-advertisement", self.service),
227 _ => format!("application/x-git-{}-result", self.service),
228 };
229 match content_type {
230 Some(ref content_type) if *content_type != expected => {
231 return Err(self.err(
232 &format!(
233 "expected a Content-Type header \
234 with `{}` but found `{}`",
235 expected, content_type
236 )[..],
237 ))
238 }
239 Some(..) => {}
240 None => {
241 return Err(self.err(
242 &format!(
243 "expected a Content-Type header \
244 with `{}` but didn't find one",
245 expected
246 )[..],
247 ))
248 }
249 }
250
251 let rdr = Cursor::new(data);
253 self.reader = Some(rdr);
254
255 if let Ok(Some(effective_url)) = h.effective_url() {
257 let new_base = if effective_url.ends_with(self.url_path) {
258 &effective_url[..effective_url.len() - self.url_path.len()]
260 } else {
261 effective_url
264 };
265 *self.base_url.lock().unwrap() = new_base.to_string();
266 }
267
268 Ok(())
269 }
270}
271
272impl Read for CurlSubtransport {
273 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
274 if self.reader.is_none() {
275 self.execute(&[])?;
276 }
277 self.reader.as_mut().unwrap().read(buf)
278 }
279}
280
281impl Write for CurlSubtransport {
282 fn write(&mut self, data: &[u8]) -> io::Result<usize> {
283 if self.reader.is_none() {
284 self.execute(data)?;
285 }
286 Ok(data.len())
287 }
288 fn flush(&mut self) -> io::Result<()> {
289 Ok(())
290 }
291}