use libc::{c_char, c_int, c_uint, c_void, size_t};
use std::ffi::{CStr, CString};
use std::mem;
use std::ptr;
use std::slice;
use std::str;
use crate::cert::Cert;
use crate::util::Binding;
use crate::{
panic, raw, Cred, CredentialType, Error, IndexerProgress, Oid, PackBuilderStage, Progress,
PushUpdate,
};
pub struct RemoteCallbacks<'a> {
push_progress: Option<Box<PushTransferProgress<'a>>>,
progress: Option<Box<IndexerProgress<'a>>>,
pack_progress: Option<Box<PackProgress<'a>>>,
credentials: Option<Box<Credentials<'a>>>,
sideband_progress: Option<Box<TransportMessage<'a>>>,
update_tips: Option<Box<UpdateTips<'a>>>,
certificate_check: Option<Box<CertificateCheck<'a>>>,
push_update_reference: Option<Box<PushUpdateReference<'a>>>,
push_negotiation: Option<Box<PushNegotiation<'a>>>,
}
pub type Credentials<'a> =
dyn FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a;
pub type TransportMessage<'a> = dyn FnMut(&[u8]) -> bool + 'a;
pub type UpdateTips<'a> = dyn FnMut(&str, Oid, Oid) -> bool + 'a;
pub type CertificateCheck<'a> =
dyn FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a;
pub enum CertificateCheckStatus {
CertificateOk,
CertificatePassthrough,
}
pub type PushUpdateReference<'a> = dyn FnMut(&str, Option<&str>) -> Result<(), Error> + 'a;
pub type PushTransferProgress<'a> = dyn FnMut(usize, usize, usize) + 'a;
pub type PackProgress<'a> = dyn FnMut(PackBuilderStage, usize, usize) + 'a;
pub type PushNegotiation<'a> = dyn FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a;
impl<'a> Default for RemoteCallbacks<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> RemoteCallbacks<'a> {
pub fn new() -> RemoteCallbacks<'a> {
RemoteCallbacks {
credentials: None,
progress: None,
pack_progress: None,
sideband_progress: None,
update_tips: None,
certificate_check: None,
push_update_reference: None,
push_progress: None,
push_negotiation: None,
}
}
pub fn credentials<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a,
{
self.credentials = Some(Box::new(cb) as Box<Credentials<'a>>);
self
}
pub fn transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(Progress<'_>) -> bool + 'a,
{
self.progress = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
self
}
pub fn sideband_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(&[u8]) -> bool + 'a,
{
self.sideband_progress = Some(Box::new(cb) as Box<TransportMessage<'a>>);
self
}
pub fn update_tips<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(&str, Oid, Oid) -> bool + 'a,
{
self.update_tips = Some(Box::new(cb) as Box<UpdateTips<'a>>);
self
}
pub fn certificate_check<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a,
{
self.certificate_check = Some(Box::new(cb) as Box<CertificateCheck<'a>>);
self
}
pub fn push_update_reference<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(&str, Option<&str>) -> Result<(), Error> + 'a,
{
self.push_update_reference = Some(Box::new(cb) as Box<PushUpdateReference<'a>>);
self
}
pub fn push_transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(usize, usize, usize) + 'a,
{
self.push_progress = Some(Box::new(cb) as Box<PushTransferProgress<'a>>);
self
}
pub fn pack_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(PackBuilderStage, usize, usize) + 'a,
{
self.pack_progress = Some(Box::new(cb) as Box<PackProgress<'a>>);
self
}
pub fn push_negotiation<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
where
F: FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a,
{
self.push_negotiation = Some(Box::new(cb) as Box<PushNegotiation<'a>>);
self
}
}
impl<'a> Binding for RemoteCallbacks<'a> {
type Raw = raw::git_remote_callbacks;
unsafe fn from_raw(_raw: raw::git_remote_callbacks) -> RemoteCallbacks<'a> {
panic!("unimplemented");
}
fn raw(&self) -> raw::git_remote_callbacks {
unsafe {
let mut callbacks: raw::git_remote_callbacks = mem::zeroed();
assert_eq!(
raw::git_remote_init_callbacks(&mut callbacks, raw::GIT_REMOTE_CALLBACKS_VERSION),
0
);
if self.progress.is_some() {
callbacks.transfer_progress = Some(transfer_progress_cb);
}
if self.credentials.is_some() {
callbacks.credentials = Some(credentials_cb);
}
if self.sideband_progress.is_some() {
callbacks.sideband_progress = Some(sideband_progress_cb);
}
if self.certificate_check.is_some() {
callbacks.certificate_check = Some(certificate_check_cb);
}
if self.push_update_reference.is_some() {
callbacks.push_update_reference = Some(push_update_reference_cb);
}
if self.push_progress.is_some() {
callbacks.push_transfer_progress = Some(push_transfer_progress_cb);
}
if self.pack_progress.is_some() {
callbacks.pack_progress = Some(pack_progress_cb);
}
if self.update_tips.is_some() {
let f: extern "C" fn(
*const c_char,
*const raw::git_oid,
*const raw::git_oid,
*mut c_void,
) -> c_int = update_tips_cb;
callbacks.update_tips = Some(f);
}
if self.push_negotiation.is_some() {
callbacks.push_negotiation = Some(push_negotiation_cb);
}
callbacks.payload = self as *const _ as *mut _;
callbacks
}
}
}
extern "C" fn credentials_cb(
ret: *mut *mut raw::git_cred,
url: *const c_char,
username_from_url: *const c_char,
allowed_types: c_uint,
payload: *mut c_void,
) -> c_int {
unsafe {
let ok = panic::wrap(|| {
let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
let callback = payload
.credentials
.as_mut()
.ok_or(raw::GIT_PASSTHROUGH as c_int)?;
*ret = ptr::null_mut();
let url = str::from_utf8(CStr::from_ptr(url).to_bytes())
.map_err(|_| raw::GIT_PASSTHROUGH as c_int)?;
let username_from_url = match crate::opt_bytes(&url, username_from_url) {
Some(username) => {
Some(str::from_utf8(username).map_err(|_| raw::GIT_PASSTHROUGH as c_int)?)
}
None => None,
};
let cred_type = CredentialType::from_bits_truncate(allowed_types as u32);
callback(url, username_from_url, cred_type).map_err(|e| {
let s = CString::new(e.to_string()).unwrap();
raw::git_error_set_str(e.class() as c_int, s.as_ptr());
e.raw_code() as c_int
})
});
match ok {
Some(Ok(cred)) => {
if allowed_types & (cred.credtype() as c_uint) != 0 {
*ret = cred.unwrap();
0
} else {
raw::GIT_PASSTHROUGH as c_int
}
}
Some(Err(e)) => e,
None => -1,
}
}
}
extern "C" fn transfer_progress_cb(
stats: *const raw::git_indexer_progress,
payload: *mut c_void,
) -> c_int {
let ok = panic::wrap(|| unsafe {
let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
let callback = match payload.progress {
Some(ref mut c) => c,
None => return true,
};
let progress = Binding::from_raw(stats);
callback(progress)
});
if ok == Some(true) {
0
} else {
-1
}
}
extern "C" fn sideband_progress_cb(str: *const c_char, len: c_int, payload: *mut c_void) -> c_int {
let ok = panic::wrap(|| unsafe {
let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
let callback = match payload.sideband_progress {
Some(ref mut c) => c,
None => return true,
};
let buf = slice::from_raw_parts(str as *const u8, len as usize);
callback(buf)
});
if ok == Some(true) {
0
} else {
-1
}
}
extern "C" fn update_tips_cb(
refname: *const c_char,
a: *const raw::git_oid,
b: *const raw::git_oid,
data: *mut c_void,
) -> c_int {
let ok = panic::wrap(|| unsafe {
let payload = &mut *(data as *mut RemoteCallbacks<'_>);
let callback = match payload.update_tips {
Some(ref mut c) => c,
None => return true,
};
let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap();
let a = Binding::from_raw(a);
let b = Binding::from_raw(b);
callback(refname, a, b)
});
if ok == Some(true) {
0
} else {
-1
}
}
extern "C" fn certificate_check_cb(
cert: *mut raw::git_cert,
_valid: c_int,
hostname: *const c_char,
data: *mut c_void,
) -> c_int {
let ok = panic::wrap(|| unsafe {
let payload = &mut *(data as *mut RemoteCallbacks<'_>);
let callback = match payload.certificate_check {
Some(ref mut c) => c,
None => return Ok(CertificateCheckStatus::CertificatePassthrough),
};
let cert = Binding::from_raw(cert);
let hostname = str::from_utf8(CStr::from_ptr(hostname).to_bytes()).unwrap();
callback(&cert, hostname)
});
match ok {
Some(Ok(CertificateCheckStatus::CertificateOk)) => 0,
Some(Ok(CertificateCheckStatus::CertificatePassthrough)) => raw::GIT_PASSTHROUGH as c_int,
Some(Err(e)) => {
let s = CString::new(e.message()).unwrap();
unsafe {
raw::git_error_set_str(e.class() as c_int, s.as_ptr());
}
e.raw_code() as c_int
}
None => {
-1
}
}
}
extern "C" fn push_update_reference_cb(
refname: *const c_char,
status: *const c_char,
data: *mut c_void,
) -> c_int {
panic::wrap(|| unsafe {
let payload = &mut *(data as *mut RemoteCallbacks<'_>);
let callback = match payload.push_update_reference {
Some(ref mut c) => c,
None => return 0,
};
let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap();
let status = if status.is_null() {
None
} else {
Some(str::from_utf8(CStr::from_ptr(status).to_bytes()).unwrap())
};
match callback(refname, status) {
Ok(()) => 0,
Err(e) => e.raw_code(),
}
})
.unwrap_or(-1)
}
extern "C" fn push_transfer_progress_cb(
progress: c_uint,
total: c_uint,
bytes: size_t,
data: *mut c_void,
) -> c_int {
panic::wrap(|| unsafe {
let payload = &mut *(data as *mut RemoteCallbacks<'_>);
let callback = match payload.push_progress {
Some(ref mut c) => c,
None => return 0,
};
callback(progress as usize, total as usize, bytes as usize);
0
})
.unwrap_or(-1)
}
extern "C" fn pack_progress_cb(
stage: raw::git_packbuilder_stage_t,
current: c_uint,
total: c_uint,
data: *mut c_void,
) -> c_int {
panic::wrap(|| unsafe {
let payload = &mut *(data as *mut RemoteCallbacks<'_>);
let callback = match payload.pack_progress {
Some(ref mut c) => c,
None => return 0,
};
let stage = Binding::from_raw(stage);
callback(stage, current as usize, total as usize);
0
})
.unwrap_or(-1)
}
extern "C" fn push_negotiation_cb(
updates: *mut *const raw::git_push_update,
len: size_t,
payload: *mut c_void,
) -> c_int {
panic::wrap(|| unsafe {
let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
let callback = match payload.push_negotiation {
Some(ref mut c) => c,
None => return 0,
};
let updates = slice::from_raw_parts(updates as *mut PushUpdate<'_>, len);
match callback(updates) {
Ok(()) => 0,
Err(e) => e.raw_code(),
}
})
.unwrap_or(-1)
}