blob: eed7be6185cd3d518de2b65a31b5e4d2bcaa18de [file] [log] [blame]
use std::{borrow::Cow, collections::BTreeMap, convert::TryFrom};
use crate::{
bstr::{BStr, BString, ByteSlice},
config,
config::tree::{gitoxide, Key, Protocol},
};
/// All allowed values of the `protocol.allow` key.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Allow {
/// Allow use this protocol.
Always,
/// Forbid using this protocol
Never,
/// Only supported if the `GIT_PROTOCOL_FROM_USER` is unset or is set to `1`.
User,
}
impl Allow {
/// Return true if we represent something like 'allow == true'.
pub fn to_bool(self, user_allowed: Option<bool>) -> bool {
match self {
Allow::Always => true,
Allow::Never => false,
Allow::User => user_allowed.unwrap_or(true),
}
}
}
impl<'a> TryFrom<Cow<'a, BStr>> for Allow {
type Error = BString;
fn try_from(v: Cow<'a, BStr>) -> Result<Self, Self::Error> {
Ok(match v.as_ref().as_bytes() {
b"never" => Allow::Never,
b"always" => Allow::Always,
b"user" => Allow::User,
unknown => return Err(unknown.into()),
})
}
}
#[derive(Debug, Clone)]
pub(crate) struct SchemePermission {
/// `None`, env-var is unset or wasn't queried, otherwise true if `GIT_PROTOCOL_FROM_USER` is `1`.
user_allowed: Option<bool>,
/// The general allow value from `protocol.allow`.
allow: Option<Allow>,
/// Per scheme allow information
allow_per_scheme: BTreeMap<gix_url::Scheme, Allow>,
}
/// Init
impl SchemePermission {
/// NOTE: _intentionally without leniency_
pub fn from_config(
config: &gix_config::File<'static>,
mut filter: fn(&gix_config::file::Metadata) -> bool,
) -> Result<Self, config::protocol::allow::Error> {
let allow: Option<Allow> = config
.string_filter_by_key("protocol.allow", &mut filter)
.map(|value| Protocol::ALLOW.try_into_allow(value, None))
.transpose()?;
let mut saw_user = allow.map_or(false, |allow| allow == Allow::User);
let allow_per_scheme = match config.sections_by_name_and_filter("protocol", &mut filter) {
Some(it) => {
let mut map = BTreeMap::default();
for (section, scheme) in it.filter_map(|section| {
section.header().subsection_name().and_then(|scheme| {
scheme
.to_str()
.ok()
.map(|scheme| (section, gix_url::Scheme::from(scheme)))
})
}) {
if let Some(value) = section
.value("allow")
.map(|value| Protocol::ALLOW.try_into_allow(value, Some(scheme.as_str())))
.transpose()?
{
saw_user |= value == Allow::User;
map.insert(scheme, value);
}
}
map
}
None => Default::default(),
};
let user_allowed = saw_user.then(|| {
config
.string_filter_by_key(gitoxide::Allow::PROTOCOL_FROM_USER.logical_name().as_str(), &mut filter)
.map_or(true, |val| val.as_ref() == "1")
});
Ok(SchemePermission {
allow,
allow_per_scheme,
user_allowed,
})
}
}
/// Access
impl SchemePermission {
pub fn allow(&self, scheme: &gix_url::Scheme) -> bool {
self.allow_per_scheme.get(scheme).or(self.allow.as_ref()).map_or_else(
|| {
use gix_url::Scheme::*;
match scheme {
File | Git | Ssh | Http | Https => true,
Ext(_) => false,
// TODO: figure out what 'ext' really entails, and what 'other' protocols are which aren't representable for us yet
}
},
|allow| allow.to_bool(self.user_allowed),
)
}
}