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 { match self { Allow::Always => true, Allow::Never => false, Allow::User => user_allowed.unwrap_or(true), } } } impl<'a> TryFrom> for Allow { type Error = BString; fn try_from(v: Cow<'a, BStr>) -> Result { 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, /// The general allow value from `protocol.allow`. allow: Option, /// Per scheme allow information allow_per_scheme: BTreeMap, } /// 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 { let allow: Option = 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() .and_then(|scheme| gix_url::Scheme::try_from(scheme).ok().map(|scheme| (section, 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), ) } }