use crate::Error;
use std::marker::PhantomData;
pub enum AuthSetting<'a> {
Username(&'a str),
DefaultUsername(&'a str),
DefaultPassword(&'a str),
SslServerTrustFile(&'a str),
SslClientCertFile(&'a str),
SslClientCertPassword(&'a str),
ConfigDir(&'a str),
ServerGroup(&'a str),
ConfigCategoryServers(&'a str),
SslServerFailures(u32),
}
pub struct AuthBaton {
ptr: *mut subversion_sys::svn_auth_baton_t,
pool: apr::Pool<'static>,
parameter_names: std::collections::HashMap<String, std::ffi::CString>,
_providers: Vec<AuthProvider>,
}
unsafe impl Send for AuthBaton {}
pub trait Credentials {
fn kind() -> &'static str
where
Self: Sized;
fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void;
fn from_raw(cred: *mut std::ffi::c_void) -> Self
where
Self: Sized;
fn as_simple(&self) -> Option<&SimpleCredentials<'_>> {
None
}
fn as_username(&self) -> Option<&UsernameCredentials<'_>> {
None
}
fn as_ssl_client_cert(&self) -> Option<&SslClientCertCredentials<'_>> {
None
}
fn as_ssl_client_cert_pw(&self) -> Option<&SslClientCertPwCredentials<'_>> {
None
}
fn as_ssl_server_trust(&self) -> Option<&SslServerTrustCredentials<'_>> {
None
}
}
pub struct SimpleCredentials<'pool> {
ptr: *mut subversion_sys::svn_auth_cred_simple_t,
_pool: std::marker::PhantomData<&'pool apr::Pool<'static>>,
}
impl<'pool> SimpleCredentials<'pool> {
pub fn username(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).username)
.to_str()
.unwrap()
}
}
pub fn password(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).password)
.to_str()
.unwrap()
}
}
pub fn set_username(&mut self, username: &str, pool: &apr::Pool) {
unsafe {
(*self.ptr).username = apr::strings::pstrdup_raw(username, pool).unwrap() as *mut _;
}
}
pub fn may_save(&self) -> bool {
unsafe { (*self.ptr).may_save != 0 }
}
pub fn new(
username: String,
password: String,
may_save: bool,
pool: &'pool apr::Pool<'pool>,
) -> Self {
let cred: *mut subversion_sys::svn_auth_cred_simple_t = pool.calloc();
unsafe {
(*cred).username = apr::strings::pstrdup_raw(&username, pool).unwrap() as *mut _;
(*cred).password = apr::strings::pstrdup_raw(&password, pool).unwrap() as *mut _;
(*cred).may_save = if may_save { 1 } else { 0 };
}
Self {
ptr: cred,
_pool: std::marker::PhantomData,
}
}
}
impl<'pool> Credentials for SimpleCredentials<'pool> {
fn kind() -> &'static str
where
Self: Sized,
{
unsafe { std::str::from_utf8_unchecked(subversion_sys::SVN_AUTH_CRED_SIMPLE) }
.trim_end_matches('\0')
}
fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void {
self.ptr as *mut std::ffi::c_void
}
fn from_raw(cred: *mut std::ffi::c_void) -> Self {
Self {
ptr: cred as *mut subversion_sys::svn_auth_cred_simple_t,
_pool: std::marker::PhantomData,
}
}
fn as_simple(&self) -> Option<&SimpleCredentials<'_>> {
Some(self)
}
}
pub struct UsernameCredentials<'pool> {
ptr: *mut subversion_sys::svn_auth_cred_username_t,
_pool: std::marker::PhantomData<&'pool apr::Pool<'static>>,
}
impl<'pool> UsernameCredentials<'pool> {
pub fn username(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).username)
.to_str()
.unwrap()
}
}
pub fn may_save(&self) -> bool {
unsafe { (*self.ptr).may_save != 0 }
}
pub fn new(username: String, may_save: bool, pool: &'pool apr::Pool<'pool>) -> Self {
let cred: *mut subversion_sys::svn_auth_cred_username_t = pool.calloc();
unsafe {
(*cred).username = apr::strings::pstrdup_raw(&username, pool).unwrap() as *mut _;
(*cred).may_save = if may_save { 1 } else { 0 };
}
Self {
ptr: cred,
_pool: std::marker::PhantomData,
}
}
}
impl<'pool> Credentials for UsernameCredentials<'pool> {
fn kind() -> &'static str
where
Self: Sized,
{
unsafe { std::str::from_utf8_unchecked(subversion_sys::SVN_AUTH_CRED_USERNAME) }
.trim_end_matches('\0')
}
fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void {
self.ptr as *mut std::ffi::c_void
}
fn from_raw(cred: *mut std::ffi::c_void) -> Self
where
Self: Sized,
{
Self {
ptr: cred as *mut subversion_sys::svn_auth_cred_username_t,
_pool: std::marker::PhantomData,
}
}
fn as_username(&self) -> Option<&UsernameCredentials<'_>> {
Some(self)
}
}
pub struct SslServerTrustCredentials<'pool> {
ptr: *mut subversion_sys::svn_auth_cred_ssl_server_trust_t,
_pool: std::marker::PhantomData<&'pool apr::Pool<'static>>,
}
impl<'pool> SslServerTrustCredentials<'pool> {
pub fn may_save(&self) -> bool {
unsafe { (*self.ptr).may_save != 0 }
}
pub fn accepted_failures(&self) -> u32 {
unsafe { (*self.ptr).accepted_failures }
}
pub fn new(may_save: bool, accepted_failures: u32, pool: &'pool apr::Pool<'pool>) -> Self {
let cred: *mut subversion_sys::svn_auth_cred_ssl_server_trust_t = pool.calloc();
unsafe {
(*cred).may_save = if may_save { 1 } else { 0 };
(*cred).accepted_failures = accepted_failures;
}
Self {
ptr: cred,
_pool: std::marker::PhantomData,
}
}
}
impl<'pool> Credentials for SslServerTrustCredentials<'pool> {
fn kind() -> &'static str
where
Self: Sized,
{
unsafe { std::str::from_utf8_unchecked(subversion_sys::SVN_AUTH_CRED_SSL_SERVER_TRUST) }
.trim_end_matches('\0')
}
fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void {
self.ptr as *mut std::ffi::c_void
}
fn from_raw(cred: *mut std::ffi::c_void) -> Self
where
Self: Sized,
{
Self {
ptr: cred as *mut subversion_sys::svn_auth_cred_ssl_server_trust_t,
_pool: std::marker::PhantomData,
}
}
fn as_ssl_server_trust(&self) -> Option<&SslServerTrustCredentials<'_>> {
Some(self)
}
}
impl AuthBaton {
#[cfg(test)]
pub(crate) fn first_credentials(
&mut self,
cred_kind: &str,
realm: &str,
) -> Result<Option<Box<dyn Credentials + '_>>, Error<'_>> {
use std::ffi::CString;
let mut creds_ptr = std::ptr::null_mut();
let mut iter_baton = std::ptr::null_mut();
let cred_kind_c = CString::new(cred_kind)?;
let realm_c = CString::new(realm)?;
let err = unsafe {
subversion_sys::svn_auth_first_credentials(
&mut creds_ptr,
&mut iter_baton,
cred_kind_c.as_ptr(),
realm_c.as_ptr(),
self.ptr,
self.pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
if creds_ptr.is_null() {
return Ok(None);
}
match cred_kind {
"svn.simple" => {
let simple_creds = SimpleCredentials::from_raw(creds_ptr);
Ok(Some(Box::new(simple_creds)))
}
"svn.username" => {
let username_creds = UsernameCredentials::from_raw(creds_ptr);
Ok(Some(Box::new(username_creds)))
}
"svn.ssl.server.trust" => {
let trust_creds = SslServerTrustCredentials::from_raw(creds_ptr);
Ok(Some(Box::new(trust_creds)))
}
_ => Ok(None),
}
}
pub fn as_mut_ptr(&mut self) -> *mut subversion_sys::svn_auth_baton_t {
self.ptr
}
pub fn set(&mut self, setting: AuthSetting) -> Result<(), Error<'static>> {
match setting {
AuthSetting::Username(value) => self.set_string_parameter("username", value),
AuthSetting::DefaultUsername(value) => {
self.set_string_parameter("svn:auth:username", value)
}
AuthSetting::SslServerTrustFile(value) => {
self.set_string_parameter("servers:global:ssl-trust-default-ca", value)
}
AuthSetting::SslClientCertFile(value) => {
self.set_string_parameter("servers:global:ssl-client-cert-file", value)
}
AuthSetting::SslClientCertPassword(value) => {
self.set_string_parameter("servers:global:ssl-client-cert-password", value)
}
AuthSetting::ConfigDir(value) => self.set_string_parameter("config-dir", value),
AuthSetting::ServerGroup(value) => self.set_string_parameter("servers:group", value),
AuthSetting::ConfigCategoryServers(value) => {
self.set_string_parameter("config:servers", value)
}
AuthSetting::DefaultPassword(value) => {
self.set_string_parameter("svn:auth:password", value)
}
AuthSetting::SslServerFailures(failures) => {
let ptr: *mut apr_sys::apr_uint32_t = self.pool.calloc();
unsafe { *ptr = failures };
unsafe {
self.set_parameter("svn:auth:ssl:failures", ptr as *const std::ffi::c_void);
}
Ok(())
}
}
}
fn set_string_parameter(
&mut self,
param_name: &str,
param_value: &str,
) -> Result<(), Error<'static>> {
let persistent_value = apr::strings::pstrdup(param_value, &self.pool)?;
unsafe {
self.set_parameter(
param_name,
persistent_value.as_ptr() as *const std::ffi::c_void,
);
}
Ok(())
}
pub fn credentials<C: Credentials>(&mut self, realm: &str) -> Result<IterState<C>, Error<'_>> {
let cred_kind = std::ffi::CString::new(C::kind()).unwrap();
let realm = std::ffi::CString::new(realm).unwrap();
let mut cred = std::ptr::null_mut();
let mut state = std::ptr::null_mut();
let iter_pool = apr::pool::Pool::new();
unsafe {
let err = subversion_sys::svn_auth_first_credentials(
&mut cred,
&mut state,
cred_kind.as_ptr(),
realm.as_ptr(),
self.ptr,
iter_pool.as_mut_ptr(),
);
Error::from_raw(err)?;
let first_creds = if cred.is_null() {
None
} else {
Some(C::from_raw(cred))
};
Ok(IterState {
ptr: state,
creds: first_creds,
pools: vec![iter_pool],
_phantom: PhantomData,
})
}
}
pub fn forget_credentials<C: Credentials>(
&mut self,
cred_kind: Option<&str>,
realm: Option<&str>,
) -> Result<(), Error<'static>> {
let cred_kind_cstring = cred_kind.map(|s| std::ffi::CString::new(s).unwrap());
let realm_cstring = realm.map(|s| std::ffi::CString::new(s).unwrap());
let cred_kind_ptr = cred_kind_cstring
.as_ref()
.map_or(std::ptr::null(), |c| c.as_ptr());
let realm_ptr = realm_cstring
.as_ref()
.map_or(std::ptr::null(), |c| c.as_ptr());
let err = std::ptr::null_mut();
let pool = apr::pool::Pool::new();
unsafe {
subversion_sys::svn_auth_forget_credentials(
self.ptr,
cred_kind_ptr,
realm_ptr,
pool.as_mut_ptr(),
);
Error::from_raw(err)?;
Ok(())
}
}
pub unsafe fn get_parameter(&mut self, name: &str) -> *const std::ffi::c_void {
if !self.parameter_names.contains_key(name) {
let name_cstring = std::ffi::CString::new(name).unwrap();
self.parameter_names.insert(name.to_string(), name_cstring);
}
let name_cstring = &self.parameter_names[name];
subversion_sys::svn_auth_get_parameter(self.ptr, name_cstring.as_ptr())
}
pub unsafe fn set_parameter(&mut self, name: &str, value: *const std::ffi::c_void) {
let name_cstring = std::ffi::CString::new(name).unwrap();
let name_ptr = name_cstring.as_ptr();
self.parameter_names.insert(name.to_string(), name_cstring);
subversion_sys::svn_auth_set_parameter(self.ptr, name_ptr, value);
}
pub fn open(providers: Vec<AuthProvider>) -> Result<Self, Error<'static>> {
let pool = apr::pool::Pool::new();
let mut baton = std::ptr::null_mut();
unsafe {
let mut provider_array = apr::tables::TypedArray::<
*const subversion_sys::svn_auth_provider_object_t,
>::new(&pool, providers.len() as i32);
for provider in &providers {
provider_array.push(provider.as_auth_provider(&pool));
}
subversion_sys::svn_auth_open(&mut baton, provider_array.as_ptr(), pool.as_mut_ptr());
Ok(Self {
ptr: baton,
pool,
parameter_names: std::collections::HashMap::new(),
_providers: providers,
})
}
}
}
pub struct IterState<C: Credentials> {
ptr: *mut subversion_sys::svn_auth_iterstate_t,
creds: Option<C>,
pools: Vec<apr::Pool<'static>>,
_phantom: PhantomData<*mut ()>,
}
impl<C: Credentials> IterState<C> {
pub fn credentials(&self) -> Option<&C> {
self.creds.as_ref()
}
pub fn save_credentials(&mut self) -> Result<(), Error<'static>> {
let pool = apr::pool::Pool::new();
let err = unsafe { subversion_sys::svn_auth_save_credentials(self.ptr, pool.as_mut_ptr()) };
Error::from_raw(err)?;
Ok(())
}
pub fn next_credentials(&mut self) -> Result<Option<C>, Error<'_>> {
let mut cred = std::ptr::null_mut();
let pool = apr::pool::Pool::new();
unsafe {
let err =
subversion_sys::svn_auth_next_credentials(&mut cred, self.ptr, pool.as_mut_ptr());
Error::from_raw(err)?;
if cred.is_null() {
return Ok(None);
}
let result = C::from_raw(cred);
self.pools.push(pool);
Ok(Some(result))
}
}
}
impl<C: Credentials> Iterator for IterState<C> {
type Item = C;
fn next(&mut self) -> Option<Self::Item> {
if let Some(creds) = self.creds.take() {
return Some(creds);
}
match self.next_credentials() {
Ok(Some(cred)) => Some(cred),
Ok(None) => None,
Err(_) => None,
}
}
}
pub trait AsAuthProvider {
fn as_auth_provider(
&self,
pool: &apr::pool::Pool,
) -> *const subversion_sys::svn_auth_provider_object_t;
}
impl AsAuthProvider for *const subversion_sys::svn_auth_provider_object_t {
fn as_auth_provider(
&self,
_pool: &apr::pool::Pool,
) -> *const subversion_sys::svn_auth_provider_object_t {
*self
}
}
impl AsAuthProvider for AuthProvider {
fn as_auth_provider(
&self,
_pool: &apr::pool::Pool,
) -> *const subversion_sys::svn_auth_provider_object_t {
self.ptr
}
}
impl AsAuthProvider for &AuthProvider {
fn as_auth_provider(
&self,
_pool: &apr::pool::Pool,
) -> *const subversion_sys::svn_auth_provider_object_t {
self.ptr
}
}
type DropperFn = unsafe fn(*mut std::ffi::c_void);
#[allow(dead_code)]
pub struct AuthProvider {
ptr: *const subversion_sys::svn_auth_provider_object_t,
pool: apr::SharedPool<'static>,
callback_baton: Option<(*mut std::ffi::c_void, DropperFn)>,
_phantom: PhantomData<*mut ()>,
}
unsafe impl Send for AuthProvider {}
impl Drop for AuthProvider {
fn drop(&mut self) {
if let Some((baton, dropper)) = self.callback_baton.take() {
unsafe { dropper(baton) };
}
}
}
impl AuthProvider {
pub fn cred_kind(&self) -> &str {
let cred_kind = unsafe { (*(*self.ptr).vtable).cred_kind };
unsafe { std::ffi::CStr::from_ptr(cred_kind).to_str().unwrap() }
}
pub fn save_credentials(
&self,
credentials: &mut impl Credentials,
realm: &str,
) -> Result<bool, Error<'static>> {
let realm = std::ffi::CString::new(realm).unwrap();
let creds = credentials.as_mut_ptr();
let pool = apr::pool::Pool::new();
let mut saved = subversion_sys::svn_boolean_t::default();
let err = unsafe {
(*(*self.ptr).vtable).save_credentials.unwrap()(
&mut saved,
creds,
(*self.ptr).provider_baton,
std::ptr::null_mut(),
realm.as_ptr(),
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(true)
}
}
pub fn get_username_provider() -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_username_provider(&mut auth_provider, pool.as_mut_ptr());
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub fn get_ssl_server_trust_file_provider() -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_ssl_server_trust_file_provider(
&mut auth_provider,
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
#[allow(dead_code)]
pub struct SslServerCertInfo {
ptr: *const subversion_sys::svn_auth_ssl_server_cert_info_t,
pool: apr::PoolHandle<'static>,
_phantom: PhantomData<*mut ()>,
}
unsafe impl Send for SslServerCertInfo {}
impl SslServerCertInfo {
pub fn hostname(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).hostname)
.to_str()
.unwrap()
}
}
pub fn fingerprint(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).fingerprint)
.to_str()
.unwrap()
}
}
pub fn valid_from(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).valid_from)
.to_str()
.unwrap()
}
}
pub fn valid_until(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).valid_until)
.to_str()
.unwrap()
}
}
pub fn issuer_dname(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).issuer_dname)
.to_str()
.unwrap()
}
}
pub fn ascii_cert(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).ascii_cert)
.to_str()
.unwrap()
}
}
pub fn dup(&self) -> Self {
let pool = apr::Pool::new();
let ptr = unsafe {
subversion_sys::svn_auth_ssl_server_cert_info_dup(self.ptr, pool.as_mut_ptr())
};
Self {
ptr,
pool: apr::PoolHandle::owned(pool),
_phantom: PhantomData,
}
}
}
pub struct SslServerTrust {
ptr: *mut subversion_sys::svn_auth_cred_ssl_server_trust_t,
pool: apr::Pool<'static>,
_phantom: PhantomData<*mut ()>,
}
unsafe impl Send for SslServerTrust {}
impl SslServerTrust {
pub fn pool(&self) -> &apr::Pool<'_> {
&self.pool
}
pub fn as_ptr(&self) -> *const subversion_sys::svn_auth_cred_ssl_server_trust_t {
self.ptr
}
pub fn as_mut_ptr(&mut self) -> *mut subversion_sys::svn_auth_cred_ssl_server_trust_t {
self.ptr
}
pub fn dup(&self) -> Self {
let pool = apr::Pool::new();
let cred = pool.calloc();
unsafe { std::ptr::copy_nonoverlapping(self.ptr, cred, 1) };
Self {
ptr: cred,
pool,
_phantom: PhantomData,
}
}
}
pub fn get_ssl_server_trust_prompt_provider(
prompt_func: &impl Fn(
&'_ str,
usize,
&'_ SslServerCertInfo,
bool,
) -> Result<SslServerTrust, Error<'static>>,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
extern "C" fn wrap_ssl_server_trust_prompt_fn(
cred: *mut *mut subversion_sys::svn_auth_cred_ssl_server_trust_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
failures: apr_sys::apr_uint32_t,
cert_info: *const subversion_sys::svn_auth_ssl_server_cert_info_t,
may_save: subversion_sys::svn_boolean_t,
pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const &dyn Fn(
&'_ str,
usize,
&'_ SslServerCertInfo,
bool,
) -> Result<SslServerTrust, crate::Error<'static>>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
let cert_info = SslServerCertInfo {
ptr: cert_info,
pool: unsafe { apr::PoolHandle::from_borrowed_raw(pool) },
_phantom: PhantomData,
};
f(
realm,
failures.try_into().unwrap(),
&cert_info,
may_save != 0,
)
.map(|creds| {
unsafe { *cred = creds.ptr };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_ssl_server_trust_prompt_provider(
&mut auth_provider,
Some(wrap_ssl_server_trust_prompt_fn),
prompt_func as *const _ as *mut std::ffi::c_void,
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
extern "C" fn wrap_ssl_server_trust_prompt_fn_boxed(
cred: *mut *mut subversion_sys::svn_auth_cred_ssl_server_trust_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
failures: apr_sys::apr_uint32_t,
cert_info: *const subversion_sys::svn_auth_ssl_server_cert_info_t,
may_save: subversion_sys::svn_boolean_t,
pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const Box<
dyn Fn(
&str,
u32,
Option<&SslServerCertInfo>,
bool,
) -> Result<Option<(u32, bool)>, crate::Error<'static>>
+ Send,
>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
let cert_info_wrap = if cert_info.is_null() {
None
} else {
Some(SslServerCertInfo {
ptr: cert_info,
pool: unsafe { apr::PoolHandle::from_borrowed_raw(pool) },
_phantom: PhantomData,
})
};
f(realm, failures, cert_info_wrap.as_ref(), may_save != 0)
.map(|result| {
match result {
Some((accepted_failures, save)) => {
let pool_ref = unsafe { apr::PoolHandle::from_borrowed_raw(pool) };
let trust_creds =
SslServerTrustCredentials::new(save, accepted_failures, &pool_ref);
unsafe { *cred = trust_creds.ptr as *mut _ };
}
None => {
unsafe { *cred = std::ptr::null_mut() };
}
}
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
unsafe fn drop_ssl_server_trust_prompt_baton(baton: *mut std::ffi::c_void) {
drop(Box::from_raw(
baton
as *mut Box<
dyn Fn(
&str,
u32,
Option<&SslServerCertInfo>,
bool,
) -> Result<Option<(u32, bool)>, crate::Error<'static>>
+ Send,
>,
));
}
pub fn get_ssl_server_trust_prompt_provider_boxed(
prompt_fn: Box<
dyn Fn(
&str,
u32,
Option<&SslServerCertInfo>,
bool,
) -> Result<Option<(u32, bool)>, crate::Error<'static>>
+ Send,
>,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
let baton = Box::into_raw(Box::new(prompt_fn));
unsafe {
subversion_sys::svn_auth_get_ssl_server_trust_prompt_provider(
&mut auth_provider,
Some(wrap_ssl_server_trust_prompt_fn_boxed),
baton as *mut std::ffi::c_void,
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: Some((
baton as *mut std::ffi::c_void,
drop_ssl_server_trust_prompt_baton,
)),
_phantom: PhantomData,
}
}
pub fn get_ssl_client_cert_file_provider() -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_ssl_client_cert_file_provider(
&mut auth_provider,
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub struct SslClientCertCredentials<'pool> {
ptr: *mut subversion_sys::svn_auth_cred_ssl_client_cert_t,
_pool: std::marker::PhantomData<&'pool apr::Pool<'static>>,
}
impl<'pool> SslClientCertCredentials<'pool> {
pub fn cert_file(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).cert_file)
.to_str()
.unwrap()
}
}
pub fn may_save(&self) -> bool {
unsafe { (*self.ptr).may_save != 0 }
}
pub fn new(cert_file: String, may_save: bool, pool: &'pool apr::Pool<'pool>) -> Self {
let cred: *mut subversion_sys::svn_auth_cred_ssl_client_cert_t = pool.calloc();
unsafe {
(*cred).cert_file = apr::strings::pstrdup_raw(&cert_file, pool).unwrap() as *mut _;
(*cred).may_save = if may_save { 1 } else { 0 };
}
Self {
ptr: cred,
_pool: std::marker::PhantomData,
}
}
}
impl<'pool> Credentials for SslClientCertCredentials<'pool> {
fn kind() -> &'static str
where
Self: Sized,
{
unsafe { std::str::from_utf8_unchecked(subversion_sys::SVN_AUTH_CRED_SSL_CLIENT_CERT) }
.trim_end_matches('\0')
}
fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void {
self.ptr as *mut std::ffi::c_void
}
fn from_raw(cred: *mut std::ffi::c_void) -> Self {
Self {
ptr: cred as *mut subversion_sys::svn_auth_cred_ssl_client_cert_t,
_pool: std::marker::PhantomData,
}
}
fn as_ssl_client_cert(&self) -> Option<&SslClientCertCredentials<'_>> {
Some(unsafe {
std::mem::transmute::<&SslClientCertCredentials<'_>, &SslClientCertCredentials<'_>>(
self,
)
})
}
}
pub struct SslClientCertPwCredentials<'pool> {
ptr: *mut subversion_sys::svn_auth_cred_ssl_client_cert_pw_t,
_pool: std::marker::PhantomData<&'pool apr::Pool<'static>>,
}
impl<'pool> SslClientCertPwCredentials<'pool> {
pub fn password(&self) -> &str {
unsafe {
std::ffi::CStr::from_ptr((*self.ptr).password)
.to_str()
.unwrap()
}
}
pub fn may_save(&self) -> bool {
unsafe { (*self.ptr).may_save != 0 }
}
pub fn new(password: String, may_save: bool, pool: &'pool apr::Pool<'pool>) -> Self {
let cred: *mut subversion_sys::svn_auth_cred_ssl_client_cert_pw_t = pool.calloc();
unsafe {
(*cred).password = apr::strings::pstrdup_raw(&password, pool).unwrap() as *mut _;
(*cred).may_save = if may_save { 1 } else { 0 };
}
Self {
ptr: cred,
_pool: std::marker::PhantomData,
}
}
}
impl<'pool> Credentials for SslClientCertPwCredentials<'pool> {
fn kind() -> &'static str
where
Self: Sized,
{
unsafe { std::str::from_utf8_unchecked(subversion_sys::SVN_AUTH_CRED_SSL_CLIENT_CERT_PW) }
.trim_end_matches('\0')
}
fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void {
self.ptr as *mut std::ffi::c_void
}
fn from_raw(cred: *mut std::ffi::c_void) -> Self {
Self {
ptr: cred as *mut subversion_sys::svn_auth_cred_ssl_client_cert_pw_t,
_pool: std::marker::PhantomData,
}
}
fn as_ssl_client_cert_pw(&self) -> Option<&SslClientCertPwCredentials<'_>> {
Some(unsafe {
std::mem::transmute::<&SslClientCertPwCredentials<'_>, &SslClientCertPwCredentials<'_>>(
self,
)
})
}
}
extern "C" fn wrap_client_cert_prompt_fn(
cred: *mut *mut subversion_sys::svn_auth_cred_ssl_client_cert_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
may_save: subversion_sys::svn_boolean_t,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const &dyn for<'a> Fn(
&'a str,
bool,
) -> Result<
SslClientCertCredentials<'a>,
crate::Error<'static>,
>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
f(realm, may_save != 0)
.map(|creds| {
unsafe { *cred = creds.ptr };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
pub fn get_ssl_client_cert_prompt_provider(
prompt_fn: &impl for<'a> Fn(
&'a str,
bool,
) -> Result<SslClientCertCredentials<'a>, crate::Error<'static>>,
retry_limit: usize,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_ssl_client_cert_prompt_provider(
&mut auth_provider,
Some(wrap_client_cert_prompt_fn),
prompt_fn as *const _ as *mut std::ffi::c_void,
retry_limit.try_into().unwrap(),
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
extern "C" fn wrap_client_cert_prompt_fn_boxed(
cred: *mut *mut subversion_sys::svn_auth_cred_ssl_client_cert_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
may_save: subversion_sys::svn_boolean_t,
pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const Box<
dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send,
>)
};
let pool_ref = unsafe { apr::PoolHandle::from_borrowed_raw(pool) };
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
f(realm, may_save != 0)
.map(|(cert_file, save)| {
let creds = SslClientCertCredentials::new(cert_file, save, &pool_ref);
unsafe { *cred = creds.ptr };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
unsafe fn drop_client_cert_prompt_baton(baton: *mut std::ffi::c_void) {
drop(Box::from_raw(
baton
as *mut Box<dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send>,
));
}
pub fn get_ssl_client_cert_prompt_provider_boxed(
prompt_fn: Box<dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send>,
retry_limit: usize,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
let baton = Box::into_raw(Box::new(prompt_fn));
unsafe {
subversion_sys::svn_auth_get_ssl_client_cert_prompt_provider(
&mut auth_provider,
Some(wrap_client_cert_prompt_fn_boxed),
baton as *mut std::ffi::c_void,
retry_limit.try_into().unwrap(),
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: Some((
baton as *mut std::ffi::c_void,
drop_client_cert_prompt_baton,
)),
_phantom: PhantomData,
}
}
extern "C" fn wrap_client_cert_pw_prompt_fn_boxed(
cred: *mut *mut subversion_sys::svn_auth_cred_ssl_client_cert_pw_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
may_save: subversion_sys::svn_boolean_t,
pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const Box<
dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send,
>)
};
let pool_ref = unsafe { apr::PoolHandle::from_borrowed_raw(pool) };
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
f(realm, may_save != 0)
.map(|(password, save)| {
let creds = SslClientCertPwCredentials::new(password, save, &pool_ref);
unsafe { *cred = creds.ptr };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
unsafe fn drop_client_cert_pw_prompt_baton(baton: *mut std::ffi::c_void) {
drop(Box::from_raw(
baton
as *mut Box<dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send>,
));
}
pub fn get_ssl_client_cert_pw_prompt_provider_boxed(
prompt_fn: Box<dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send>,
retry_limit: usize,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
let baton = Box::into_raw(Box::new(prompt_fn));
unsafe {
subversion_sys::svn_auth_get_ssl_client_cert_pw_prompt_provider(
&mut auth_provider,
Some(wrap_client_cert_pw_prompt_fn_boxed),
baton as *mut std::ffi::c_void,
retry_limit.try_into().unwrap(),
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: Some((
baton as *mut std::ffi::c_void,
drop_client_cert_pw_prompt_baton,
)),
_phantom: PhantomData,
}
}
pub fn get_ssl_client_cert_pw_file_provider(
prompt_fn: Option<&impl Fn(&str) -> Result<bool, crate::Error<'static>>>,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_ssl_client_cert_pw_file_provider2(
&mut auth_provider,
if prompt_fn.is_some() {
Some(wrap_plaintext_passphrase_prompt)
} else {
None
},
if let Some(f) = prompt_fn {
f as *const _ as *mut std::ffi::c_void
} else {
std::ptr::null_mut()
},
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub fn get_ssl_client_cert_pw_file_provider_boxed(
prompt_fn: Option<Box<dyn Fn(&str) -> Result<bool, crate::Error> + Send>>,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
let baton = if let Some(func) = prompt_fn {
Box::into_raw(func) as *mut std::ffi::c_void
} else {
std::ptr::null_mut()
};
unsafe {
subversion_sys::svn_auth_get_ssl_client_cert_pw_file_provider2(
&mut auth_provider,
if !baton.is_null() {
Some(wrap_plaintext_passphrase_prompt_boxed)
} else {
None
},
baton,
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub fn get_simple_prompt_provider<'pool>(
prompt_fn: &impl Fn(
&'_ str,
Option<&'_ str>,
bool,
) -> Result<SimpleCredentials<'pool>, crate::Error<'static>>,
retry_limit: usize,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
extern "C" fn wrap_simple_prompt_provider(
credentials: *mut *mut subversion_sys::svn_auth_cred_simple_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
username: *const std::ffi::c_char,
may_save: subversion_sys::svn_boolean_t,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const &dyn for<'a> Fn(
&'a str,
Option<&'a str>,
bool,
)
-> Result<SimpleCredentials<'a>, crate::Error<'static>>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
let username = if username.is_null() {
None
} else {
Some(unsafe { std::ffi::CStr::from_ptr(username).to_str().unwrap() })
};
f(realm, username, may_save != 0)
.map(|creds| {
unsafe { *credentials = creds.ptr as *mut _ };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_simple_prompt_provider(
&mut auth_provider,
Some(wrap_simple_prompt_provider),
prompt_fn as *const _ as *mut std::ffi::c_void,
retry_limit.try_into().unwrap(),
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub fn get_simple_prompt_provider_boxed(
prompt_fn: Box<
dyn Fn(&str, Option<&str>, bool) -> Result<(String, String, bool), crate::Error<'static>>
+ Send,
>,
retry_limit: usize,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
let baton = Box::into_raw(Box::new(prompt_fn)) as *mut std::ffi::c_void;
extern "C" fn wrap_simple_prompt_provider_boxed(
credentials: *mut *mut subversion_sys::svn_auth_cred_simple_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
username: *const std::ffi::c_char,
may_save: subversion_sys::svn_boolean_t,
pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const Box<
dyn Fn(
&str,
Option<&str>,
bool,
)
-> Result<(String, String, bool), crate::Error<'static>>
+ Send,
>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
let username_str = if username.is_null() {
None
} else {
Some(unsafe { std::ffi::CStr::from_ptr(username).to_str().unwrap() })
};
let svn_pool = unsafe { apr::PoolHandle::from_borrowed_raw(pool) };
f(realm, username_str, may_save != 0)
.map(|(user, pass, save)| {
let creds = SimpleCredentials::new(user, pass, save, &svn_pool);
unsafe { *credentials = creds.ptr as *mut _ };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
unsafe fn drop_simple_prompt_baton(baton: *mut std::ffi::c_void) {
drop(Box::from_raw(
baton
as *mut Box<
dyn Fn(
&str,
Option<&str>,
bool,
)
-> Result<(String, String, bool), crate::Error<'static>>
+ Send,
>,
));
}
unsafe {
subversion_sys::svn_auth_get_simple_prompt_provider(
&mut auth_provider,
Some(wrap_simple_prompt_provider_boxed),
baton,
retry_limit.try_into().unwrap(),
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: Some((baton, drop_simple_prompt_baton)),
_phantom: PhantomData,
}
}
pub fn get_username_prompt_provider(
prompt_fn: &impl Fn(&str, bool) -> Result<String, crate::Error<'static>>,
retry_limit: usize,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
extern "C" fn wrap_username_prompt_provider(
credentials: *mut *mut subversion_sys::svn_auth_cred_username_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
may_save: subversion_sys::svn_boolean_t,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton as *const &dyn Fn(&str, bool) -> Result<String, crate::Error<'static>>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
f(realm, may_save != 0)
.map(|username| {
let username_cstr = std::ffi::CString::new(username).unwrap();
let username_ptr = unsafe { apr_sys::apr_pstrdup(_pool, username_cstr.as_ptr()) };
let creds = subversion_sys::svn_auth_cred_username_t {
username: username_ptr,
may_save,
};
unsafe { *credentials = Box::into_raw(Box::new(creds)) };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_username_prompt_provider(
&mut auth_provider,
Some(wrap_username_prompt_provider),
prompt_fn as *const _ as *mut std::ffi::c_void,
retry_limit.try_into().unwrap(),
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub fn get_username_prompt_provider_boxed(
prompt_fn: Box<dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send>,
retry_limit: usize,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
let baton = Box::into_raw(Box::new(prompt_fn)) as *mut std::ffi::c_void;
extern "C" fn wrap_username_prompt_provider_boxed(
credentials: *mut *mut subversion_sys::svn_auth_cred_username_t,
baton: *mut std::ffi::c_void,
realmstring: *const std::ffi::c_char,
may_save: subversion_sys::svn_boolean_t,
pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton
as *const Box<
dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send,
>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
let svn_pool = unsafe { apr::PoolHandle::from_borrowed_raw(pool) };
f(realm, may_save != 0)
.map(|(username, save)| {
let creds = UsernameCredentials::new(username, save, &svn_pool);
unsafe { *credentials = creds.ptr as *mut _ };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
unsafe fn drop_username_prompt_baton(baton: *mut std::ffi::c_void) {
drop(Box::from_raw(
baton
as *mut Box<
dyn Fn(&str, bool) -> Result<(String, bool), crate::Error<'static>> + Send,
>,
));
}
unsafe {
subversion_sys::svn_auth_get_username_prompt_provider(
&mut auth_provider,
Some(wrap_username_prompt_provider_boxed),
baton,
retry_limit.try_into().unwrap(),
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: Some((baton, drop_username_prompt_baton)),
_phantom: PhantomData,
}
}
extern "C" fn wrap_plaintext_passphrase_prompt_boxed(
may_save_plaintext: *mut subversion_sys::svn_boolean_t,
realmstring: *const std::ffi::c_char,
baton: *mut std::ffi::c_void,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe {
&*(baton as *const Box<dyn Fn(&str) -> Result<bool, crate::Error<'static>> + Send>)
};
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
f(realm)
.map(|b| {
unsafe { *may_save_plaintext = if b { 1 } else { 0 } };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
extern "C" fn wrap_plaintext_passphrase_prompt(
may_save_plaintext: *mut subversion_sys::svn_boolean_t,
realmstring: *const std::ffi::c_char,
baton: *mut std::ffi::c_void,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let f = unsafe { &*(baton as *const &dyn Fn(&str) -> Result<bool, crate::Error<'static>>) };
let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() };
f(realm)
.map(|b| {
unsafe { *may_save_plaintext = if b { 1 } else { 0 } };
std::ptr::null_mut()
})
.unwrap_or_else(|e| unsafe { e.into_raw() })
}
pub fn get_simple_provider(
plaintext_prompt_func: Option<&impl Fn(&str) -> Result<bool, crate::Error<'static>>>,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
unsafe {
subversion_sys::svn_auth_get_simple_provider2(
&mut auth_provider,
if plaintext_prompt_func.is_some() {
Some(wrap_plaintext_passphrase_prompt)
} else {
None
},
if let Some(f) = plaintext_prompt_func {
f as *const _ as *mut std::ffi::c_void
} else {
std::ptr::null_mut()
},
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub fn get_simple_provider_boxed(
plaintext_prompt_func: Option<Box<dyn Fn(&str) -> Result<bool, crate::Error<'static>> + Send>>,
) -> AuthProvider {
let mut auth_provider = std::ptr::null_mut();
let pool = apr::SharedPool::new();
let baton = if let Some(func) = plaintext_prompt_func {
Box::into_raw(func) as *mut std::ffi::c_void
} else {
std::ptr::null_mut()
};
unsafe {
subversion_sys::svn_auth_get_simple_provider2(
&mut auth_provider,
if !baton.is_null() {
Some(wrap_plaintext_passphrase_prompt_boxed)
} else {
None
},
baton,
pool.as_mut_ptr(),
);
}
AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
}
}
pub fn get_platform_specific_provider(
provider_name: &str,
provider_type: &str,
) -> Result<AuthProvider, crate::Error<'static>> {
let mut auth_provider = std::ptr::null_mut();
let provider_name = std::ffi::CString::new(provider_name).unwrap();
let provider_type = std::ffi::CString::new(provider_type).unwrap();
let pool = apr::SharedPool::new();
let err = unsafe {
subversion_sys::svn_auth_get_platform_specific_provider(
&mut auth_provider,
provider_name.as_ptr(),
provider_type.as_ptr(),
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(AuthProvider {
ptr: auth_provider,
pool,
callback_baton: None,
_phantom: PhantomData,
})
}
pub fn get_platform_specific_client_providers(
config: Option<&mut crate::config::Config>,
) -> Result<Vec<AuthProvider>, Error<'static>> {
let pool = apr::SharedPool::new();
let mut providers = std::ptr::null_mut();
let config_ptr = match config {
Some(c) => c.as_mut_ptr(),
None => std::ptr::null_mut(),
};
let err = unsafe {
subversion_sys::svn_auth_get_platform_specific_client_providers(
&mut providers,
config_ptr,
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
let providers = unsafe {
apr::tables::TypedArray::<*mut subversion_sys::svn_auth_provider_object_t>::from_ptr(
providers,
)
};
Ok(providers
.iter()
.map(|p| AuthProvider {
ptr: p as *const _,
pool: pool.clone(), callback_baton: None,
_phantom: PhantomData,
})
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_credentials_creation() {
let pool = apr::Pool::new();
let creds =
SimpleCredentials::new("testuser".to_string(), "testpass".to_string(), true, &pool);
assert_eq!(creds.username(), "testuser");
assert_eq!(creds.password(), "testpass");
assert!(creds.may_save());
}
#[test]
fn test_simple_credentials_set_username() {
let pool = apr::Pool::new();
let mut creds =
SimpleCredentials::new("olduser".to_string(), "pass".to_string(), false, &pool);
creds.set_username("newuser", &pool);
assert_eq!(creds.username(), "newuser");
assert!(!creds.may_save());
}
#[test]
fn test_simple_credentials_kind() {
let kind = SimpleCredentials::kind();
assert_eq!(kind, "svn.simple");
}
#[test]
fn test_simple_credentials_as_mut_ptr() {
let pool = apr::Pool::new();
let mut creds = SimpleCredentials::new("user".to_string(), "pass".to_string(), true, &pool);
let ptr = creds.as_mut_ptr();
assert!(!ptr.is_null());
unsafe {
let raw_creds = ptr as *mut subversion_sys::svn_auth_cred_simple_t;
let username = std::ffi::CStr::from_ptr((*raw_creds).username)
.to_str()
.unwrap();
assert_eq!(username, "user");
}
}
#[test]
fn test_simple_credentials_from_raw() {
let pool = apr::Pool::new();
let raw_cred: *mut subversion_sys::svn_auth_cred_simple_t = pool.calloc();
unsafe {
(*raw_cred).username = apr::strings::pstrdup_raw("raw_user", &pool).unwrap() as *mut _;
(*raw_cred).password = apr::strings::pstrdup_raw("raw_pass", &pool).unwrap() as *mut _;
(*raw_cred).may_save = 1;
}
let creds = SimpleCredentials::from_raw(raw_cred as *mut std::ffi::c_void);
assert_eq!(creds.username(), "raw_user");
assert_eq!(creds.password(), "raw_pass");
assert!(creds.may_save());
}
#[test]
fn test_username_credentials_creation() {
let pool = apr::Pool::new();
let creds = UsernameCredentials::new("testuser".to_string(), true, &pool);
assert_eq!(creds.username(), "testuser");
assert!(creds.may_save());
}
#[test]
fn test_username_credentials_may_save_false() {
let pool = apr::Pool::new();
let creds = UsernameCredentials::new("user".to_string(), false, &pool);
assert_eq!(creds.username(), "user");
assert!(!creds.may_save());
}
#[test]
fn test_username_credentials_kind() {
let kind = UsernameCredentials::kind();
assert_eq!(kind, "svn.username");
}
#[test]
fn test_username_credentials_as_mut_ptr() {
let pool = apr::Pool::new();
let mut creds = UsernameCredentials::new("user".to_string(), true, &pool);
let ptr = creds.as_mut_ptr();
assert!(!ptr.is_null());
unsafe {
let raw_creds = ptr as *mut subversion_sys::svn_auth_cred_username_t;
let username = std::ffi::CStr::from_ptr((*raw_creds).username)
.to_str()
.unwrap();
assert_eq!(username, "user");
assert_eq!((*raw_creds).may_save, 1);
}
}
#[test]
fn test_username_credentials_from_raw() {
let pool = apr::Pool::new();
let raw_cred: *mut subversion_sys::svn_auth_cred_username_t = pool.calloc();
unsafe {
(*raw_cred).username =
apr::strings::pstrdup_raw("raw_username", &pool).unwrap() as *mut _;
(*raw_cred).may_save = 0;
}
let creds = UsernameCredentials::from_raw(raw_cred as *mut std::ffi::c_void);
assert_eq!(creds.username(), "raw_username");
assert!(!creds.may_save());
}
#[test]
fn test_ssl_server_trust_credentials_creation() {
let pool = apr::Pool::new();
let creds = SslServerTrustCredentials::new(true, 0x01, &pool);
assert!(creds.may_save());
assert_eq!(creds.accepted_failures(), 0x01);
}
#[test]
fn test_ssl_server_trust_credentials_may_save_false() {
let pool = apr::Pool::new();
let creds = SslServerTrustCredentials::new(false, 0xFF, &pool);
assert!(!creds.may_save());
assert_eq!(creds.accepted_failures(), 0xFF);
}
#[test]
fn test_ssl_server_trust_credentials_kind() {
let kind = SslServerTrustCredentials::kind();
assert_eq!(kind, "svn.ssl.server");
}
#[test]
fn test_ssl_server_trust_credentials_as_mut_ptr() {
let pool = apr::Pool::new();
let mut creds = SslServerTrustCredentials::new(true, 0x42, &pool);
let ptr = creds.as_mut_ptr();
assert!(!ptr.is_null());
unsafe {
let raw_creds = ptr as *mut subversion_sys::svn_auth_cred_ssl_server_trust_t;
assert_eq!((*raw_creds).may_save, 1);
assert_eq!((*raw_creds).accepted_failures, 0x42);
}
}
#[test]
fn test_ssl_server_trust_credentials_from_raw() {
let pool = apr::Pool::new();
let raw_cred: *mut subversion_sys::svn_auth_cred_ssl_server_trust_t = pool.calloc();
unsafe {
(*raw_cred).may_save = 0;
(*raw_cred).accepted_failures = 0xAB;
}
let creds = SslServerTrustCredentials::from_raw(raw_cred as *mut std::ffi::c_void);
assert!(!creds.may_save());
assert_eq!(creds.accepted_failures(), 0xAB);
}
#[test]
fn test_credentials_trait_polymorphism() {
let pool = apr::Pool::new();
let simple = SimpleCredentials::new("user".to_string(), "pass".to_string(), true, &pool);
let username = UsernameCredentials::new("user2".to_string(), false, &pool);
let trust = SslServerTrustCredentials::new(true, 0x01, &pool);
let creds: Vec<Box<dyn Credentials + '_>> =
vec![Box::new(simple), Box::new(username), Box::new(trust)];
if let Some(simple_creds) = creds[0].as_simple() {
assert_eq!(simple_creds.username(), "user");
assert_eq!(simple_creds.password(), "pass");
assert!(simple_creds.may_save());
} else {
panic!("Failed to downcast to SimpleCredentials");
}
if let Some(username_creds) = creds[1].as_username() {
assert_eq!(username_creds.username(), "user2");
assert!(!username_creds.may_save());
} else {
panic!("Failed to downcast to UsernameCredentials");
}
if let Some(trust_creds) = creds[2].as_ssl_server_trust() {
assert!(trust_creds.may_save());
assert_eq!(trust_creds.accepted_failures(), 0x01);
} else {
panic!("Failed to downcast to SslServerTrustCredentials");
}
assert!(creds[0].as_username().is_none());
assert!(creds[0].as_ssl_server_trust().is_none());
assert!(creds[1].as_simple().is_none());
assert!(creds[1].as_ssl_server_trust().is_none());
assert!(creds[2].as_simple().is_none());
assert!(creds[2].as_username().is_none());
}
#[test]
fn test_credentials_mixed_downcast_and_query() {
let pool = apr::Pool::new();
let creds: Vec<Box<dyn Credentials + '_>> = vec![
Box::new(SimpleCredentials::new(
"alice".to_string(),
"alice123".to_string(),
true,
&pool,
)),
Box::new(UsernameCredentials::new("bob".to_string(), false, &pool)),
Box::new(SslServerTrustCredentials::new(true, 0x404, &pool)),
Box::new(SimpleCredentials::new(
"charlie".to_string(),
"ch@rl!3".to_string(),
false,
&pool,
)),
];
if let Some(simple) = creds[0].as_simple() {
assert_eq!(simple.username(), "alice");
assert_eq!(simple.password(), "alice123");
assert!(simple.may_save());
} else {
panic!("Failed to downcast index 0 to SimpleCredentials");
}
if let Some(username) = creds[1].as_username() {
assert_eq!(username.username(), "bob");
assert!(!username.may_save());
} else {
panic!("Failed to downcast index 1 to UsernameCredentials");
}
if let Some(trust) = creds[2].as_ssl_server_trust() {
assert!(trust.may_save());
assert_eq!(trust.accepted_failures(), 0x404);
} else {
panic!("Failed to downcast index 2 to SslServerTrustCredentials");
}
if let Some(simple) = creds[3].as_simple() {
assert_eq!(simple.username(), "charlie");
assert_eq!(simple.password(), "ch@rl!3");
assert!(!simple.may_save());
} else {
panic!("Failed to downcast index 3 to SimpleCredentials");
}
assert!(creds[0].as_username().is_none());
assert!(creds[0].as_ssl_server_trust().is_none());
assert!(creds[1].as_simple().is_none());
assert!(creds[1].as_ssl_server_trust().is_none());
assert!(creds[2].as_simple().is_none());
assert!(creds[2].as_username().is_none());
}
#[test]
fn test_auth_provider_creation() {
let username_provider = get_username_provider();
assert!(!username_provider.ptr.is_null());
let simple_provider =
get_simple_provider(None::<&fn(&str) -> Result<bool, Error<'static>>>);
assert!(!simple_provider.ptr.is_null());
let ssl_trust_provider = get_ssl_server_trust_file_provider();
assert!(!ssl_trust_provider.ptr.is_null());
let ssl_cert_provider = get_ssl_client_cert_file_provider();
assert!(!ssl_cert_provider.ptr.is_null());
}
#[test]
fn test_auth_provider_cred_kind() {
let provider = get_username_provider();
let kind = provider.cred_kind();
assert!(kind.contains("username"));
}
#[test]
fn test_auth_baton_open() {
let providers = vec![
get_username_provider(),
get_simple_provider(None::<&fn(&str) -> Result<bool, Error<'static>>>),
];
AuthBaton::open(providers).unwrap();
}
#[test]
fn test_auth_baton_parameter_safety() {
let providers = vec![get_username_provider()];
let mut baton = AuthBaton::open(providers).unwrap();
let test_key = "test_param";
let test_value = "test_value\0";
unsafe {
baton.set_parameter(test_key, test_value.as_ptr() as *const _);
let retrieved = baton.get_parameter(test_key);
assert!(!retrieved.is_null());
}
}
#[test]
fn test_ssl_server_cert_info_dup() {
fn _assert_send<T: Send>() {}
_assert_send::<SslServerCertInfo>();
}
#[test]
fn test_ssl_server_trust_dup() {
fn _assert_send<T: Send>() {}
_assert_send::<SslServerTrust>();
}
#[test]
fn test_ssl_client_cert_credentials_not_send() {
fn _assert_not_send<T>() {}
_assert_not_send::<SslClientCertCredentials>();
}
#[test]
fn test_platform_specific_providers() {
let _providers = get_platform_specific_client_providers(None).unwrap();
}
#[test]
fn test_simple_prompt_provider() {
let prompt_fn = |_realm: &str,
_username: Option<&str>,
_may_save: bool|
-> Result<SimpleCredentials, Error<'static>> {
Err(Error::from_message("Not implemented"))
};
let provider = get_simple_prompt_provider(&prompt_fn, 3);
assert!(!provider.ptr.is_null());
}
#[test]
fn test_username_prompt_provider() {
let prompt_fn =
|_realm: &str, _may_save: bool| Ok::<_, Error>("prompted_username".to_string());
let provider = get_username_prompt_provider(&prompt_fn, 3);
assert!(!provider.ptr.is_null());
}
#[test]
fn test_ssl_client_cert_prompt_provider_boxed() {
let callback =
Box::new(|_realm: &str, _may_save: bool| Ok(("/path/to/cert".to_string(), true)));
let provider = get_ssl_client_cert_prompt_provider_boxed(callback, 3);
let mut auth_baton = AuthBaton::open(vec![provider]).unwrap();
let iter_state = auth_baton
.credentials::<SslClientCertCredentials>("")
.unwrap();
let creds = iter_state.credentials().unwrap();
assert_eq!(creds.cert_file(), "/path/to/cert");
assert!(creds.may_save());
}
#[test]
fn test_sequential_auth_sessions_no_corruption() {
{
let callback =
Box::new(|_realm: &str, _may_save: bool| Ok(("/path/to/cert".to_string(), false)));
let provider = get_ssl_client_cert_prompt_provider_boxed(callback, 0);
let mut baton = AuthBaton::open(vec![provider]).unwrap();
let iter = baton
.credentials::<SslClientCertCredentials>("realm1")
.unwrap();
assert_eq!(iter.credentials().unwrap().cert_file(), "/path/to/cert");
}
{
let i = std::cell::Cell::new(0);
let callback = Box::new(move |_realm: &str, _may_save: bool| {
i.set(i.get() + 1);
Ok((format!("user{}", i.get()), false))
});
let provider = get_username_prompt_provider_boxed(callback, 2);
let mut baton = AuthBaton::open(vec![provider]).unwrap();
let mut iter = baton.credentials::<UsernameCredentials>("realm2").unwrap();
assert_eq!(iter.credentials().unwrap().username(), "user1");
let c2 = iter.next_credentials().unwrap().unwrap();
assert_eq!(c2.username(), "user2");
let c3 = iter.next_credentials().unwrap().unwrap();
assert_eq!(c3.username(), "user3");
assert!(iter.next_credentials().unwrap().is_none());
}
}
#[test]
fn test_ssl_client_cert_pw_prompt_provider_boxed() {
let callback = Box::new(|_realm: &str, _may_save: bool| Ok(("s3cret".to_string(), true)));
let provider = get_ssl_client_cert_pw_prompt_provider_boxed(callback, 3);
let mut auth_baton = AuthBaton::open(vec![provider]).unwrap();
let iter_state = auth_baton
.credentials::<SslClientCertPwCredentials>("")
.unwrap();
let creds = iter_state.credentials().unwrap();
assert_eq!(creds.password(), "s3cret");
assert!(creds.may_save());
}
#[test]
fn test_auth_provider_as_trait() {
let provider = get_username_provider();
let pool = apr::Pool::new();
let ptr = provider.as_auth_provider(&pool);
assert!(!ptr.is_null());
assert_eq!(ptr, provider.ptr);
let ptr2 = provider.as_auth_provider(&pool);
assert_eq!(ptr, ptr2);
}
#[test]
fn test_boxed_simple_prompt_returns_credentials() {
let callback = Box::new(|_realm: &str, _username: Option<&str>, may_save: bool| {
Ok((
"test_user".to_string(),
"secure_password".to_string(),
may_save,
))
});
let provider = get_simple_prompt_provider_boxed(callback, 3);
let mut auth_baton = AuthBaton::open(vec![provider]).unwrap();
let creds = auth_baton
.first_credentials("svn.simple", "test_realm")
.unwrap();
if let Some(creds) = creds {
if let Some(simple_creds) = creds.as_simple() {
assert_eq!(simple_creds.username(), "test_user");
assert_eq!(simple_creds.password(), "secure_password");
assert!(simple_creds.may_save());
} else {
panic!("Expected SimpleCredentials");
}
} else {
panic!("Expected credentials");
}
}
#[test]
fn test_boxed_username_prompt_returns_username() {
let expected_username = "test_boxed_username";
let callback = Box::new(
move |realm: &str, may_save: bool| -> Result<(String, bool), crate::Error> {
assert_eq!(realm, "username_realm");
assert!(may_save);
Ok((expected_username.to_string(), may_save))
},
);
let provider = get_username_prompt_provider_boxed(callback, 3);
let mut auth_baton = AuthBaton::open(vec![provider]).unwrap();
let creds = auth_baton
.first_credentials("svn.username", "username_realm")
.unwrap();
if let Some(creds) = creds {
if let Some(username_creds) = creds.as_username() {
assert_eq!(username_creds.username(), expected_username);
assert!(username_creds.may_save());
} else {
panic!("Expected UsernameCredentials");
}
} else {
panic!("Expected credentials");
}
}
#[test]
fn test_boxed_simple_prompt_with_initial_username() {
let callback = Box::new(|_realm: &str, username: Option<&str>, may_save: bool| {
Ok((
username.unwrap_or("initial_user").to_string(),
"password123".to_string(),
may_save,
))
});
let provider = get_simple_prompt_provider_boxed(callback, 3);
let mut auth_baton = AuthBaton::open(vec![provider]).unwrap();
let creds = auth_baton
.first_credentials("svn.simple", "test_realm")
.unwrap();
if let Some(creds) = creds {
if let Some(simple_creds) = creds.as_simple() {
assert!(!simple_creds.username().is_empty());
assert_eq!(simple_creds.password(), "password123");
assert!(simple_creds.may_save());
} else {
panic!("Expected SimpleCredentials");
}
} else {
panic!("Expected credentials");
}
}
#[test]
fn test_boxed_callback_may_save_false() {
let callback = Box::new(|_realm: &str, _username: Option<&str>, _may_save: bool| {
Ok((
"user".to_string(),
"pass".to_string(),
false, ))
});
let provider = get_simple_prompt_provider_boxed(callback, 1);
let mut auth_baton = AuthBaton::open(vec![provider]).unwrap();
let creds = auth_baton.first_credentials("svn.simple", "realm").unwrap();
if let Some(creds) = creds {
if let Some(simple_creds) = creds.as_simple() {
assert_eq!(simple_creds.username(), "user");
assert_eq!(simple_creds.password(), "pass");
assert!(
!simple_creds.may_save(),
"Should be false as set by callback"
);
} else {
panic!("Expected SimpleCredentials");
}
} else {
panic!("Expected credentials");
}
}
#[test]
fn test_boxed_provider_error_handling() {
let callback = Box::new(|_realm: &str, _username: Option<&str>, _may_save: bool| {
Err::<(String, String, bool), _>(crate::Error::from_message("Authentication failed"))
});
let provider = get_simple_prompt_provider_boxed(callback, 1);
let mut auth_baton = AuthBaton::open(vec![provider]).unwrap();
let creds = auth_baton.first_credentials("svn.simple", "error_realm");
assert!(
creds.is_err(),
"Expected error from callback to be propagated"
);
}
#[test]
fn test_multiple_boxed_providers_precedence() {
let provider1 = get_simple_prompt_provider_boxed(
Box::new(|realm, _, _| {
if realm == "realm1" {
Ok(("user1".to_string(), "pass1".to_string(), true))
} else {
Err(crate::Error::from_message("Wrong realm"))
}
}),
1,
);
let provider2 = get_simple_prompt_provider_boxed(
Box::new(|_, _, _| Ok(("user2".to_string(), "pass2".to_string(), true))),
1,
);
let mut auth_baton = AuthBaton::open(vec![provider1, provider2]).unwrap();
{
let creds1 = auth_baton
.first_credentials("svn.simple", "realm1")
.unwrap();
if let Some(creds) = creds1 {
if let Some(simple_creds) = creds.as_simple() {
assert_eq!(simple_creds.username(), "user1");
assert_eq!(simple_creds.password(), "pass1");
assert!(simple_creds.may_save());
} else {
panic!("Expected SimpleCredentials for realm1");
}
} else {
panic!("Expected credentials for realm1");
}
}
let creds2_result = auth_baton.first_credentials("svn.simple", "realm2");
match creds2_result {
Ok(Some(creds)) => {
if let Some(simple_creds) = creds.as_simple() {
assert_eq!(simple_creds.username(), "user2");
assert_eq!(simple_creds.password(), "pass2");
assert!(simple_creds.may_save());
} else {
panic!("Expected SimpleCredentials for realm2");
}
}
Ok(None) => {
panic!("Expected credentials from second provider");
}
Err(_) => {
}
}
}
}