git2/
remote_callbacks.rs

1use libc::{c_char, c_int, c_uint, c_void, size_t};
2use std::ffi::CStr;
3use std::mem;
4use std::ptr;
5use std::slice;
6use std::str;
7
8use crate::cert::Cert;
9use crate::util::Binding;
10use crate::{
11    panic, raw, Cred, CredentialType, Error, IndexerProgress, Oid, PackBuilderStage, Progress,
12    PushUpdate,
13};
14
15/// A structure to contain the callbacks which are invoked when a repository is
16/// being updated or downloaded.
17///
18/// These callbacks are used to manage facilities such as authentication,
19/// transfer progress, etc.
20pub struct RemoteCallbacks<'a> {
21    push_progress: Option<Box<PushTransferProgress<'a>>>,
22    progress: Option<Box<IndexerProgress<'a>>>,
23    pack_progress: Option<Box<PackProgress<'a>>>,
24    credentials: Option<Box<Credentials<'a>>>,
25    sideband_progress: Option<Box<TransportMessage<'a>>>,
26    update_tips: Option<Box<UpdateTips<'a>>>,
27    certificate_check: Option<Box<CertificateCheck<'a>>>,
28    push_update_reference: Option<Box<PushUpdateReference<'a>>>,
29    push_negotiation: Option<Box<PushNegotiation<'a>>>,
30}
31
32/// Callback used to acquire credentials for when a remote is fetched.
33///
34/// * `url` - the resource for which the credentials are required.
35/// * `username_from_url` - the username that was embedded in the URL, or `None`
36///                         if it was not included.
37/// * `allowed_types` - a bitmask stating which cred types are OK to return.
38pub type Credentials<'a> =
39    dyn FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a;
40
41/// Callback for receiving messages delivered by the transport.
42///
43/// The return value indicates whether the network operation should continue.
44pub type TransportMessage<'a> = dyn FnMut(&[u8]) -> bool + 'a;
45
46/// Callback for whenever a reference is updated locally.
47pub type UpdateTips<'a> = dyn FnMut(&str, Oid, Oid) -> bool + 'a;
48
49/// Callback for a custom certificate check.
50///
51/// The first argument is the certificate received on the connection.
52/// Certificates are typically either an SSH or X509 certificate.
53///
54/// The second argument is the hostname for the connection is passed as the last
55/// argument.
56pub type CertificateCheck<'a> =
57    dyn FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a;
58
59/// The return value for the [`RemoteCallbacks::certificate_check`] callback.
60pub enum CertificateCheckStatus {
61    /// Indicates that the certificate should be accepted.
62    CertificateOk,
63    /// Indicates that the certificate callback is neither accepting nor
64    /// rejecting the certificate. The result of the certificate checks
65    /// built-in to libgit2 will be used instead.
66    CertificatePassthrough,
67}
68
69/// Callback for each updated reference on push.
70///
71/// The first argument here is the `refname` of the reference, and the second is
72/// the status message sent by a server. If the status is `Some` then the update
73/// was rejected by the remote server with a reason why.
74pub type PushUpdateReference<'a> = dyn FnMut(&str, Option<&str>) -> Result<(), Error> + 'a;
75
76/// Callback for push transfer progress
77///
78/// Parameters:
79/// * current
80/// * total
81/// * bytes
82pub type PushTransferProgress<'a> = dyn FnMut(usize, usize, usize) + 'a;
83
84/// Callback for pack progress
85///
86/// Be aware that this is called inline with pack building operations,
87/// so performance may be affected.
88///
89/// Parameters:
90/// * stage
91/// * current
92/// * total
93pub type PackProgress<'a> = dyn FnMut(PackBuilderStage, usize, usize) + 'a;
94
95/// The callback is called once between the negotiation step and the upload.
96///
97/// The argument is a slice containing the updates which will be sent as
98/// commands to the destination.
99///
100/// The push is cancelled if an error is returned.
101pub type PushNegotiation<'a> = dyn FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a;
102
103impl<'a> Default for RemoteCallbacks<'a> {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109impl<'a> RemoteCallbacks<'a> {
110    /// Creates a new set of empty callbacks
111    pub fn new() -> RemoteCallbacks<'a> {
112        RemoteCallbacks {
113            credentials: None,
114            progress: None,
115            pack_progress: None,
116            sideband_progress: None,
117            update_tips: None,
118            certificate_check: None,
119            push_update_reference: None,
120            push_progress: None,
121            push_negotiation: None,
122        }
123    }
124
125    /// The callback through which to fetch credentials if required.
126    ///
127    /// # Example
128    ///
129    /// Prepare a callback to authenticate using the `$HOME/.ssh/id_rsa` SSH key, and
130    /// extracting the username from the URL (i.e. git@github.com:rust-lang/git2-rs.git):
131    ///
132    /// ```no_run
133    /// use git2::{Cred, RemoteCallbacks};
134    /// use std::env;
135    ///
136    /// let mut callbacks = RemoteCallbacks::new();
137    /// callbacks.credentials(|_url, username_from_url, _allowed_types| {
138    ///   Cred::ssh_key(
139    ///     username_from_url.unwrap(),
140    ///     None,
141    ///     std::path::Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())),
142    ///     None,
143    ///   )
144    /// });
145    /// ```
146    pub fn credentials<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
147    where
148        F: FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a,
149    {
150        self.credentials = Some(Box::new(cb) as Box<Credentials<'a>>);
151        self
152    }
153
154    /// The callback through which progress is monitored.
155    pub fn transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
156    where
157        F: FnMut(Progress<'_>) -> bool + 'a,
158    {
159        self.progress = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
160        self
161    }
162
163    /// Textual progress from the remote.
164    ///
165    /// Text sent over the progress side-band will be passed to this function
166    /// (this is the 'counting objects' output).
167    pub fn sideband_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
168    where
169        F: FnMut(&[u8]) -> bool + 'a,
170    {
171        self.sideband_progress = Some(Box::new(cb) as Box<TransportMessage<'a>>);
172        self
173    }
174
175    /// Each time a reference is updated locally, the callback will be called
176    /// with information about it.
177    pub fn update_tips<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
178    where
179        F: FnMut(&str, Oid, Oid) -> bool + 'a,
180    {
181        self.update_tips = Some(Box::new(cb) as Box<UpdateTips<'a>>);
182        self
183    }
184
185    /// If certificate verification fails, then this callback will be invoked to
186    /// let the caller make the final decision of whether to allow the
187    /// connection to proceed.
188    pub fn certificate_check<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
189    where
190        F: FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a,
191    {
192        self.certificate_check = Some(Box::new(cb) as Box<CertificateCheck<'a>>);
193        self
194    }
195
196    /// Set a callback to get invoked for each updated reference on a push.
197    ///
198    /// The first argument to the callback is the name of the reference and the
199    /// second is a status message sent by the server. If the status is `Some`
200    /// then the push was rejected.
201    pub fn push_update_reference<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
202    where
203        F: FnMut(&str, Option<&str>) -> Result<(), Error> + 'a,
204    {
205        self.push_update_reference = Some(Box::new(cb) as Box<PushUpdateReference<'a>>);
206        self
207    }
208
209    /// The callback through which progress of push transfer is monitored
210    ///
211    /// Parameters:
212    /// * current
213    /// * total
214    /// * bytes
215    pub fn push_transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
216    where
217        F: FnMut(usize, usize, usize) + 'a,
218    {
219        self.push_progress = Some(Box::new(cb) as Box<PushTransferProgress<'a>>);
220        self
221    }
222
223    /// Function to call with progress information during pack building.
224    ///
225    /// Be aware that this is called inline with pack building operations,
226    /// so performance may be affected.
227    ///
228    /// Parameters:
229    /// * stage
230    /// * current
231    /// * total
232    pub fn pack_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
233    where
234        F: FnMut(PackBuilderStage, usize, usize) + 'a,
235    {
236        self.pack_progress = Some(Box::new(cb) as Box<PackProgress<'a>>);
237        self
238    }
239
240    /// The callback is called once between the negotiation step and the upload.
241    ///
242    /// The argument to the callback is a slice containing the updates which
243    /// will be sent as commands to the destination.
244    ///
245    /// The push is cancelled if the callback returns an error.
246    pub fn push_negotiation<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
247    where
248        F: FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a,
249    {
250        self.push_negotiation = Some(Box::new(cb) as Box<PushNegotiation<'a>>);
251        self
252    }
253}
254
255impl<'a> Binding for RemoteCallbacks<'a> {
256    type Raw = raw::git_remote_callbacks;
257    unsafe fn from_raw(_raw: raw::git_remote_callbacks) -> RemoteCallbacks<'a> {
258        panic!("unimplemented");
259    }
260
261    fn raw(&self) -> raw::git_remote_callbacks {
262        unsafe {
263            let mut callbacks: raw::git_remote_callbacks = mem::zeroed();
264            assert_eq!(
265                raw::git_remote_init_callbacks(&mut callbacks, raw::GIT_REMOTE_CALLBACKS_VERSION),
266                0
267            );
268            if self.progress.is_some() {
269                callbacks.transfer_progress = Some(transfer_progress_cb);
270            }
271            if self.credentials.is_some() {
272                callbacks.credentials = Some(credentials_cb);
273            }
274            if self.sideband_progress.is_some() {
275                callbacks.sideband_progress = Some(sideband_progress_cb);
276            }
277            if self.certificate_check.is_some() {
278                callbacks.certificate_check = Some(certificate_check_cb);
279            }
280            if self.push_update_reference.is_some() {
281                callbacks.push_update_reference = Some(push_update_reference_cb);
282            }
283            if self.push_progress.is_some() {
284                callbacks.push_transfer_progress = Some(push_transfer_progress_cb);
285            }
286            if self.pack_progress.is_some() {
287                callbacks.pack_progress = Some(pack_progress_cb);
288            }
289            if self.update_tips.is_some() {
290                let f: extern "C" fn(
291                    *const c_char,
292                    *const raw::git_oid,
293                    *const raw::git_oid,
294                    *mut c_void,
295                ) -> c_int = update_tips_cb;
296                callbacks.update_tips = Some(f);
297            }
298            if self.push_negotiation.is_some() {
299                callbacks.push_negotiation = Some(push_negotiation_cb);
300            }
301            callbacks.payload = self as *const _ as *mut _;
302            callbacks
303        }
304    }
305}
306
307extern "C" fn credentials_cb(
308    ret: *mut *mut raw::git_cred,
309    url: *const c_char,
310    username_from_url: *const c_char,
311    allowed_types: c_uint,
312    payload: *mut c_void,
313) -> c_int {
314    unsafe {
315        let ok = panic::wrap(|| {
316            let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
317            let callback = payload
318                .credentials
319                .as_mut()
320                .ok_or(raw::GIT_PASSTHROUGH as c_int)?;
321            *ret = ptr::null_mut();
322            let url = str::from_utf8(CStr::from_ptr(url).to_bytes())
323                .map_err(|_| raw::GIT_PASSTHROUGH as c_int)?;
324            let username_from_url = match crate::opt_bytes(&url, username_from_url) {
325                Some(username) => {
326                    Some(str::from_utf8(username).map_err(|_| raw::GIT_PASSTHROUGH as c_int)?)
327                }
328                None => None,
329            };
330
331            let cred_type = CredentialType::from_bits_truncate(allowed_types as u32);
332
333            callback(url, username_from_url, cred_type).map_err(|e| e.raw_set_git_error())
334        });
335        match ok {
336            Some(Ok(cred)) => {
337                // Turns out it's a memory safety issue if we pass through any
338                // and all credentials into libgit2
339                if allowed_types & (cred.credtype() as c_uint) != 0 {
340                    *ret = cred.unwrap();
341                    0
342                } else {
343                    raw::GIT_PASSTHROUGH as c_int
344                }
345            }
346            Some(Err(e)) => e,
347            None => -1,
348        }
349    }
350}
351
352extern "C" fn transfer_progress_cb(
353    stats: *const raw::git_indexer_progress,
354    payload: *mut c_void,
355) -> c_int {
356    let ok = panic::wrap(|| unsafe {
357        let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
358        let callback = match payload.progress {
359            Some(ref mut c) => c,
360            None => return true,
361        };
362        let progress = Binding::from_raw(stats);
363        callback(progress)
364    });
365    if ok == Some(true) {
366        0
367    } else {
368        -1
369    }
370}
371
372extern "C" fn sideband_progress_cb(str: *const c_char, len: c_int, payload: *mut c_void) -> c_int {
373    let ok = panic::wrap(|| unsafe {
374        let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
375        let callback = match payload.sideband_progress {
376            Some(ref mut c) => c,
377            None => return true,
378        };
379        let buf = slice::from_raw_parts(str as *const u8, len as usize);
380        callback(buf)
381    });
382    if ok == Some(true) {
383        0
384    } else {
385        -1
386    }
387}
388
389extern "C" fn update_tips_cb(
390    refname: *const c_char,
391    a: *const raw::git_oid,
392    b: *const raw::git_oid,
393    data: *mut c_void,
394) -> c_int {
395    let ok = panic::wrap(|| unsafe {
396        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
397        let callback = match payload.update_tips {
398            Some(ref mut c) => c,
399            None => return true,
400        };
401        let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap();
402        let a = Binding::from_raw(a);
403        let b = Binding::from_raw(b);
404        callback(refname, a, b)
405    });
406    if ok == Some(true) {
407        0
408    } else {
409        -1
410    }
411}
412
413extern "C" fn certificate_check_cb(
414    cert: *mut raw::git_cert,
415    _valid: c_int,
416    hostname: *const c_char,
417    data: *mut c_void,
418) -> c_int {
419    let ok = panic::wrap(|| unsafe {
420        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
421        let callback = match payload.certificate_check {
422            Some(ref mut c) => c,
423            None => return Ok(CertificateCheckStatus::CertificatePassthrough),
424        };
425        let cert = Binding::from_raw(cert);
426        let hostname = str::from_utf8(CStr::from_ptr(hostname).to_bytes()).unwrap();
427        callback(&cert, hostname)
428    });
429    match ok {
430        Some(Ok(CertificateCheckStatus::CertificateOk)) => 0,
431        Some(Ok(CertificateCheckStatus::CertificatePassthrough)) => raw::GIT_PASSTHROUGH as c_int,
432        Some(Err(e)) => unsafe { e.raw_set_git_error() },
433        None => {
434            // Panic. The *should* get resumed by some future call to check().
435            -1
436        }
437    }
438}
439
440extern "C" fn push_update_reference_cb(
441    refname: *const c_char,
442    status: *const c_char,
443    data: *mut c_void,
444) -> c_int {
445    panic::wrap(|| unsafe {
446        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
447        let callback = match payload.push_update_reference {
448            Some(ref mut c) => c,
449            None => return 0,
450        };
451        let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap();
452        let status = if status.is_null() {
453            None
454        } else {
455            Some(str::from_utf8(CStr::from_ptr(status).to_bytes()).unwrap())
456        };
457        match callback(refname, status) {
458            Ok(()) => 0,
459            Err(e) => e.raw_set_git_error(),
460        }
461    })
462    .unwrap_or(-1)
463}
464
465extern "C" fn push_transfer_progress_cb(
466    progress: c_uint,
467    total: c_uint,
468    bytes: size_t,
469    data: *mut c_void,
470) -> c_int {
471    panic::wrap(|| unsafe {
472        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
473        let callback = match payload.push_progress {
474            Some(ref mut c) => c,
475            None => return 0,
476        };
477
478        callback(progress as usize, total as usize, bytes as usize);
479
480        0
481    })
482    .unwrap_or(-1)
483}
484
485extern "C" fn pack_progress_cb(
486    stage: raw::git_packbuilder_stage_t,
487    current: c_uint,
488    total: c_uint,
489    data: *mut c_void,
490) -> c_int {
491    panic::wrap(|| unsafe {
492        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
493        let callback = match payload.pack_progress {
494            Some(ref mut c) => c,
495            None => return 0,
496        };
497
498        let stage = Binding::from_raw(stage);
499
500        callback(stage, current as usize, total as usize);
501
502        0
503    })
504    .unwrap_or(-1)
505}
506
507extern "C" fn push_negotiation_cb(
508    updates: *mut *const raw::git_push_update,
509    len: size_t,
510    payload: *mut c_void,
511) -> c_int {
512    panic::wrap(|| unsafe {
513        let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
514        let callback = match payload.push_negotiation {
515            Some(ref mut c) => c,
516            None => return 0,
517        };
518
519        let updates = slice::from_raw_parts(updates as *mut PushUpdate<'_>, len);
520        match callback(updates) {
521            Ok(()) => 0,
522            Err(e) => e.raw_set_git_error(),
523        }
524    })
525    .unwrap_or(-1)
526}