#![allow(deprecated)]
use std::{
ffi::CStr,
io::{self, prelude::*},
panic::{self, UnwindSafe},
str::Utf8Error,
thread,
};
use ffi;
use libc;
use static_assertions::assert_obj_safe;
use crate::{edit, utils::FdWriter, Data, Error, Result};
assert_obj_safe!(PassphraseProvider);
assert_obj_safe!(ProgressReporter);
assert_obj_safe!(StatusHandler);
assert_obj_safe!(EditInteractor);
assert_obj_safe!(Interactor);
#[derive(Debug, Copy, Clone)]
pub struct PassphraseRequest<'a> {
uid_hint: Option<&'a CStr>,
desc: Option<&'a CStr>,
pub prev_attempt_failed: bool,
}
impl<'a> PassphraseRequest<'a> {
pub fn user_id_hint(&self) -> Result<&'a str, Option<Utf8Error>> {
self.uid_hint
.map_or(Err(None), |s| s.to_str().map_err(Some))
}
pub fn user_id_hint_raw(&self) -> Option<&'a CStr> {
self.uid_hint
}
pub fn description(&self) -> Result<&'a str, Option<Utf8Error>> {
self.desc.map_or(Err(None), |s| s.to_str().map_err(Some))
}
pub fn description_raw(&self) -> Option<&'a CStr> {
self.desc
}
}
pub trait PassphraseProvider: UnwindSafe + Send {
fn get_passphrase(&mut self, request: PassphraseRequest<'_>, out: &mut dyn Write)
-> Result<()>;
}
impl<T: UnwindSafe + Send> PassphraseProvider for T
where
T: FnMut(PassphraseRequest<'_>, &mut dyn io::Write) -> Result<()>,
{
fn get_passphrase(
&mut self,
request: PassphraseRequest<'_>,
out: &mut dyn Write,
) -> Result<()> {
(*self)(request, out)
}
}
#[derive(Debug, Copy, Clone)]
pub struct ProgressInfo<'a> {
what: Option<&'a CStr>,
pub typ: i64,
pub current: i64,
pub total: i64,
}
impl<'a> ProgressInfo<'a> {
pub fn what(&self) -> Result<&'a str, Option<Utf8Error>> {
self.what.map_or(Err(None), |s| s.to_str().map_err(Some))
}
pub fn what_raw(&self) -> Option<&'a CStr> {
self.what
}
}
pub trait ProgressReporter: UnwindSafe + Send {
fn report(&mut self, info: ProgressInfo<'_>);
}
impl<T: UnwindSafe + Send> ProgressReporter for T
where
T: FnMut(ProgressInfo<'_>),
{
fn report(&mut self, info: ProgressInfo<'_>) {
(*self)(info);
}
}
pub trait StatusHandler: UnwindSafe + Send {
fn handle(&mut self, keyword: Option<&CStr>, args: Option<&CStr>) -> Result<()>;
}
impl<T: UnwindSafe + Send> StatusHandler for T
where
T: FnMut(Option<&CStr>, Option<&CStr>) -> Result<()>,
{
fn handle(&mut self, keyword: Option<&CStr>, args: Option<&CStr>) -> Result<()> {
(*self)(keyword, args)
}
}
#[derive(Debug)]
pub struct EditInteractionStatus<'a> {
pub code: edit::StatusCode,
args: Option<&'a CStr>,
pub response: &'a mut Data<'a>,
}
impl<'a> EditInteractionStatus<'a> {
pub fn args(&self) -> Result<&'a str, Option<Utf8Error>> {
match self.args {
Some(s) => s.to_str().map_err(Some),
None => Err(None),
}
}
pub fn args_raw(&self) -> Option<&'a CStr> {
self.args
}
}
#[deprecated(since = "0.9.2")]
pub trait EditInteractor: UnwindSafe + Send {
fn interact(
&mut self,
status: EditInteractionStatus<'_>,
out: Option<&mut dyn Write>,
) -> Result<()>;
}
#[derive(Debug)]
pub struct InteractionStatus<'a> {
keyword: Option<&'a CStr>,
args: Option<&'a CStr>,
pub response: &'a mut Data<'a>,
}
impl<'a> InteractionStatus<'a> {
pub fn keyword(&self) -> Result<&'a str, Option<Utf8Error>> {
self.keyword.map_or(Err(None), |s| s.to_str().map_err(Some))
}
pub fn keyword_raw(&self) -> Option<&'a CStr> {
self.keyword
}
pub fn args(&self) -> Result<&'a str, Option<Utf8Error>> {
self.args.map_or(Err(None), |s| s.to_str().map_err(Some))
}
pub fn args_raw(&self) -> Option<&'a CStr> {
self.args
}
}
pub trait Interactor: UnwindSafe + Send {
fn interact(
&mut self,
status: InteractionStatus<'_>,
out: Option<&mut dyn Write>,
) -> Result<(), Error>;
}
pub(crate) struct Hook<T>(Option<thread::Result<T>>);
impl<T> Drop for Hook<T> {
fn drop(&mut self) {
if let Some(Err(err)) = self.0.take() {
panic::resume_unwind(err);
}
}
}
impl<T> From<T> for Hook<T> {
fn from(hook: T) -> Self {
Self(Some(Ok(hook)))
}
}
impl<T: UnwindSafe> Hook<T> {
fn update<F>(&mut self, f: F) -> ffi::gpgme_error_t
where
F: UnwindSafe + FnOnce(&mut T) -> Result<()>,
{
let mut provider = match self.0.take() {
Some(Ok(p)) => p,
other => {
self.0 = other;
return ffi::GPG_ERR_GENERAL;
}
};
match panic::catch_unwind(move || {
let result = f(&mut provider);
(provider, result)
}) {
Ok((provider, result)) => {
self.0 = Some(Ok(provider));
result.err().map_or(0, |err| err.raw())
}
Err(err) => {
self.0 = Some(Err(err));
ffi::GPG_ERR_GENERAL
}
}
}
}
pub(crate) struct PassphraseCbGuard {
pub ctx: ffi::gpgme_ctx_t,
pub old: (ffi::gpgme_passphrase_cb_t, *mut libc::c_void),
}
impl Drop for PassphraseCbGuard {
fn drop(&mut self) {
unsafe {
ffi::gpgme_set_passphrase_cb(self.ctx, self.old.0, self.old.1);
}
}
}
pub(crate) struct ProgressCbGuard {
pub ctx: ffi::gpgme_ctx_t,
pub old: (ffi::gpgme_progress_cb_t, *mut libc::c_void),
}
impl Drop for ProgressCbGuard {
fn drop(&mut self) {
unsafe {
ffi::gpgme_set_progress_cb(self.ctx, self.old.0, self.old.1);
}
}
}
pub(crate) struct StatusCbGuard {
pub ctx: ffi::gpgme_ctx_t,
pub old: (ffi::gpgme_status_cb_t, *mut libc::c_void),
}
impl Drop for StatusCbGuard {
fn drop(&mut self) {
unsafe {
ffi::gpgme_set_status_cb(self.ctx, self.old.0, self.old.1);
}
}
}
pub(crate) struct InteractorHook<'a, I> {
pub inner: Hook<I>,
pub response: *mut Data<'a>,
}
pub(crate) unsafe extern "C" fn passphrase_cb<P: PassphraseProvider>(
hook: *mut libc::c_void,
uid_hint: *const libc::c_char,
info: *const libc::c_char,
was_bad: libc::c_int,
fd: libc::c_int,
) -> ffi::gpgme_error_t {
(*hook.cast::<Hook<P>>()).update(move |h| {
let info = PassphraseRequest {
uid_hint: uid_hint.as_ref().map(|s| CStr::from_ptr(s)),
desc: info.as_ref().map(|s| CStr::from_ptr(s)),
prev_attempt_failed: was_bad != 0,
};
let mut writer = FdWriter::new(fd);
h.get_passphrase(info, &mut writer)
.and_then(|_| writer.write_all(b"\n").map_err(Error::from))
})
}
pub(crate) unsafe extern "C" fn progress_cb<H: ProgressReporter>(
hook: *mut libc::c_void,
what: *const libc::c_char,
typ: libc::c_int,
current: libc::c_int,
total: libc::c_int,
) {
(*hook.cast::<Hook<H>>()).update(move |h| {
let info = ProgressInfo {
what: what.as_ref().map(|s| CStr::from_ptr(s)),
typ: typ.into(),
current: current.into(),
total: total.into(),
};
h.report(info);
Ok(())
});
}
pub(crate) unsafe extern "C" fn status_cb<H: StatusHandler>(
hook: *mut libc::c_void,
keyword: *const libc::c_char,
args: *const libc::c_char,
) -> ffi::gpgme_error_t {
(*hook.cast::<Hook<H>>()).update(move |h| {
let keyword = keyword.as_ref().map(|s| CStr::from_ptr(s));
let args = args.as_ref().map(|s| CStr::from_ptr(s));
h.handle(args, keyword)
})
}
pub(crate) unsafe extern "C" fn edit_cb<E: EditInteractor>(
hook: *mut libc::c_void,
status: ffi::gpgme_status_code_t,
args: *const libc::c_char,
fd: libc::c_int,
) -> ffi::gpgme_error_t {
let hook = &mut *hook.cast::<InteractorHook<'_, E>>();
let response = hook.response;
hook.inner.update(move |h| {
let status = EditInteractionStatus {
code: edit::StatusCode::from_raw(status),
args: args.as_ref().map(|s| CStr::from_ptr(s)),
response: &mut *response,
};
if fd < 0 {
h.interact(status, None)
} else {
h.interact(status, Some(&mut FdWriter::new(fd)))
}
})
}
pub(crate) unsafe extern "C" fn interact_cb<I: Interactor>(
hook: *mut libc::c_void,
keyword: *const libc::c_char,
args: *const libc::c_char,
fd: libc::c_int,
) -> ffi::gpgme_error_t {
let hook = &mut *hook.cast::<InteractorHook<'_, I>>();
let response = hook.response;
hook.inner.update(move |h| {
let status = InteractionStatus {
keyword: keyword.as_ref().map(|s| CStr::from_ptr(s)),
args: args.as_ref().map(|s| CStr::from_ptr(s)),
response: &mut *response,
};
if fd < 0 {
h.interact(status, None)
} else {
h.interact(status, Some(&mut FdWriter::new(fd)))
}
})
}