1use std::error;
2use std::io;
3use std::io::prelude::*;
4use std::sync::{Arc, Mutex, Once};
5use ureq::Agent;
6use ureq::AgentBuilder;
7use ureq::Proxy;
8use url::Url;
9
10use log::{debug, info};
11
12use git2::transport::{Service, SmartSubtransport, SmartSubtransportStream, Transport};
13use git2::Error;
14
15#[derive(Default)]
16struct UreqTransport {
17 base_url: Arc<Mutex<String>>,
22 proxy: Option<Proxy>,
23}
24
25struct UreqSubtransport {
26 service: &'static str,
27 url_path: &'static str,
28 base_url: Arc<Mutex<String>>,
29 method: &'static str,
30 reader: Option<Box<dyn Read + Send>>,
31 sent_request: bool,
32 client: Agent,
33}
34
35pub unsafe fn register(proxy: Option<String>) {
36 static INIT: Once = Once::new();
37
38 let proxy = proxy.map(|s| Proxy::new(s).ok()).flatten();
39 let p = proxy.clone();
40
41 INIT.call_once(move || {
42 git2::transport::register("http", move |remote| factory(remote, proxy.as_ref())).unwrap();
43 git2::transport::register("https", move |remote| factory(remote, p.as_ref())).unwrap();
44 });
45}
46
47fn factory(remote: &git2::Remote<'_>, proxy: Option<&Proxy>) -> Result<Transport, Error> {
48 Transport::smart(remote, true, UreqTransport::new(proxy.cloned()))
49}
50
51impl UreqTransport {
52 pub(crate) fn new(proxy: Option<Proxy>) -> Self {
53 Self {
54 proxy,
55 ..Default::default()
56 }
57 }
58}
59
60impl SmartSubtransport for UreqTransport {
61 fn action(
62 &self,
63 url: &str,
64 action: Service,
65 ) -> Result<Box<dyn SmartSubtransportStream>, Error> {
66 let mut base_url = self.base_url.lock().unwrap();
67 if base_url.len() == 0 {
68 *base_url = url.to_string();
69 }
70 let (service, path, method) = match action {
71 Service::UploadPackLs => ("upload-pack", "/info/refs?service=git-upload-pack", "GET"),
72 Service::UploadPack => ("upload-pack", "/git-upload-pack", "POST"),
73 Service::ReceivePackLs => {
74 ("receive-pack", "/info/refs?service=git-receive-pack", "GET")
75 }
76 Service::ReceivePack => ("receive-pack", "/git-receive-pack", "POST"),
77 };
78 info!("action {} {}", service, path);
79 Ok(Box::new(UreqSubtransport {
80 service,
81 url_path: path,
82 base_url: self.base_url.clone(),
83 method,
84 reader: None,
85 sent_request: false,
86 client: self
87 .proxy
88 .clone()
89 .map(|p| AgentBuilder::new().proxy(p))
90 .unwrap_or_else(AgentBuilder::new)
91 .build(),
92 }))
93 }
94
95 fn close(&self) -> Result<(), Error> {
96 Ok(())
97 }
98}
99
100impl UreqSubtransport {
101 fn err<E: Into<Box<dyn error::Error + Send + Sync>>>(&self, err: E) -> io::Error {
102 io::Error::new(io::ErrorKind::Other, err)
103 }
104
105 fn execute(&mut self, data: &[u8]) -> io::Result<()> {
106 if self.sent_request {
107 return Err(self.err("already sent HTTP request"));
108 }
109
110 let agent = format!("git/1.0 (git2-ureq {})", env!("CARGO_PKG_VERSION"));
111
112 let url = format!("{}{}", self.base_url.lock().unwrap(), self.url_path);
114 let parsed = Url::parse(&url).map_err(|_| self.err("invalid url, failed to parse"))?;
115 let host = match parsed.host_str() {
116 Some(host) => host,
117 None => return Err(self.err("invalid url, did not have a host")),
118 };
119
120 debug!("request to {}", url);
122 let request = self
123 .client
124 .request(self.method, &url)
125 .set("User-Agent", &agent)
126 .set("Host", &host)
127 .set("Expect", "");
128 let request = if data.is_empty() {
129 request.set("Accept", "*/*")
130 } else {
131 request
132 .set(
133 "Accept",
134 &format!("application/x-git-{}-result", self.service),
135 )
136 .set(
137 "Conent-Type",
138 &format!("application/x-git-{}-request", self.service),
139 )
140 };
141
142 let response = request.send(data).unwrap();
143 let content_type = response.header("Content-Type");
144
145 let code = response.status();
146 if code != 200 {
147 return Err(self.err(&format!("failed to receive HTTP 200 response: got {code}",)[..]));
148 }
149
150 let expected = match self.method {
152 "GET" => format!("application/x-git-{}-advertisement", self.service),
153 _ => format!("application/x-git-{}-result", self.service),
154 };
155
156 if let Some(content_type) = content_type {
157 if content_type != expected {
158 return Err(self.err(
159 &format!(
160 "expected a Content-Type header with `{expected}` but found `{content_type}`",
161 )[..],
162 ));
163 }
164 } else {
165 return Err(
166 self.err(
167 &format!(
168 "expected a Content-Type header with `{expected}` but didn't find one"
169 )[..],
170 ),
171 );
172 }
173
174 self.reader = Some(Box::new(response.into_reader()));
176
177 Ok(())
178 }
179}
180
181impl Read for UreqSubtransport {
182 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
183 if self.reader.is_none() {
184 self.execute(&[])?;
185 }
186
187 self.reader.as_mut().unwrap().read(buf)
188 }
189}
190
191impl Write for UreqSubtransport {
192 fn write(&mut self, data: &[u8]) -> io::Result<usize> {
193 if self.reader.is_none() {
194 self.execute(data)?;
195 }
196 Ok(data.len())
197 }
198
199 fn flush(&mut self) -> io::Result<()> {
200 Ok(())
201 }
202}