Compare commits

...

6 Commits

Author SHA1 Message Date
Avril 1348b0b9df
Added more convenience methods and trait impls to `PluginTrust<"a>`.
2 years ago
Avril f462d11025
Made `lookup_plugin_name()` return an `IntoIterator` for if we allow dirs to be plugins in the future. (Currently, it will always be just `Option`)
2 years ago
Avril 0a7e4286b3
Special cased rules for ownership for now.
2 years ago
Avril 430bc78072
Started: `UntrustedPathRules` path-check function pointer table. Currently incomplete, and does not handle multiple masks (i.e X | Y -> X || Y).
2 years ago
Avril b566caa361
Added symbol names and (TODO) signatures.
2 years ago
Avril 7e8a4ab553
Started plugin lookup trust rules, paths, and env-var derived paths.
2 years ago

@ -13,10 +13,23 @@ codegen-units = 1
panic = "unwind"
[dependencies]
bitflags = { version = "1.3.2", optional = true }
getrandom = "0.1"
lazy_static = { version = "1.4.0", optional = true }
libc = { version = "0.2.126", optional = true }
readable-perms = "0.1.3"
regex = { version = "1.6.0", optional = true }
[features]
default = ["plugin-ffi"]
# Dynamically allocate and populate all memory needed for `--bytes` at runtime instead of streaming in fixed size blocks.
#
# This can slightly improve total performance for large outputs, at the cost of a proportionally, arbitrary sized memory allocation along with a proportionally increasing wait while the entire area is populated.
bytes-dynamic = []
bytes-dynamic = []
# Allow plugin-loading at runtime with `[@<plugin-name>]... <options...>`
plugin = ["libc", "bitflags", "lazy_static", "regex"]
# Allow non-Rust (C interface) plugin symbol resolution.
plugin-ffi = ["plugin"]

@ -6,16 +6,21 @@ use getrandom::*;
mod parse;
mod r#impl;
#[cfg(feature="plugin")] mod plugin;
const BYTES_BUFFER_SIZE: usize = 4096;
fn get<T: Default>() -> Result<T, Error>
fn get<T>() -> Result<T, Error>
{
let mut value: T = Default::default();
unsafe {
let mut slice = std::slice::from_raw_parts_mut(&mut value as *mut T as *mut u8, std::mem::size_of::<T>());
use core::mem::MaybeUninit;
let mut value: MaybeUninit<T> = MaybeUninit::uninit();
let value = unsafe {
let mut slice = std::slice::from_raw_parts_mut(value.as_mut_ptr() as *mut u8, std::mem::size_of::<T>());
populate(&mut slice)?;
}
value.assume_init()
};
Ok(value)
}

@ -0,0 +1 @@
//! The plugin API.

@ -0,0 +1,54 @@
//! Loads dynamic object plugins for `rngcli` and loads the correct symbol entry point for adding the plugin.
use super::*;
/// The expected function signatures and ABIs of loader/unloader functions found via symbol lookup.
pub mod signatures {
/// The expected signature of the plugin's loader function symbol.
pub type PluginLoaderFunction = unsafe extern "Rust" fn () -> (); //TODO
/// The expected signature of the plugin's unloader function symbol (if it exists.)
pub type PluginUnloaderFunction = unsafe extern "Rust" fn () -> (); //TODO
#[cfg(feature="plugin-ffi")] pub use super::ffi::signatures::*;
}
/// Contains C-interface symbols and their corresponding signatures.
#[cfg(feature="plugin-ffi")]
mod ffi {
/// The names of various symbols needed for lookups in a C-interface plugin's executable object.
///
/// The means of lookup is via `dlsym()`.
pub mod symbols {
/// The C function symbol name to search for if the Rust one was not found which handles registering the plugin.
///
/// The signature of this function **must** be `ForeignPluginLoaderFunction`.
pub static FFI_PLUGIN_SYMBOL_NAME_ADD: &'static str = "_rngcli_plugin_add_c";
/// The C function symbol name to search for if the Rust one was not found which handles unloading the plugin. It is optional to include this (as the Rust one is too.)
/// The signature of this function **must** be `ForeignPluginUnloaderFunction`.
pub static FFI_PLUGIN_SYMBOL_NAME_REMOVE: &'static str = "_rngcli_plugin_remove_c";
}
/// The expected function signatures of a C-interface plugin's loader/unloader symbols found via symbol lookup.
pub mod signatures {
/// The expected signature of the C-interface plugin's loader function symbol.
pub type ForeignPluginLoaderFunction = unsafe extern "C" fn () -> (); //TODO
/// The expected signature of the C-interface plugin's unloader function symbol (if it exists.)
pub type ForeignPluginUnloaderFunction = unsafe extern "C" fn () -> (); //TODO
}
}
/// The names of various symbols needed for lookups in a plugin's executable object.
///
/// The means of the lookup is via `dlsym()`.
pub mod symbols {
/// The Rust function symbol name (non-mangled) to search for in the plugin executable object which handles registering the plugin.
///
/// The signature of this function **must** be `PluginLoaderFunction`.
pub static RUST_PLUGIN_SYMBOL_NAME_ADD: &'static str = "rngcli_plugin_add";
/// The (optional) Rust function symbol name (non-mangled) to search for in the plugin which is ran when the plugin is unloaded.
///
/// The signature of this function **must** be `PluginUnloaderFunction`.
pub static RUST_PLUGIN_SYMBOL_NAME_REMOVE: &'static str = "rngcli_plugin_remove";
#[cfg(feature="plugin-ffi")] pub use super::ffi::symbols::*;
}
use signatures::*;
//TODO: Figure out what signature to use.

@ -0,0 +1,7 @@
//! Allow loading plugins at runtime.
//TODO: Move this to a library crate and re-export it as `plugin::api`
pub mod api;
mod loader;
mod searcher;

@ -0,0 +1,730 @@
//! Searches paths for the plugin name to find it's shared object file
use super::*;
use std::path::{
PathBuf,
Path,
};
use std::{
fs, io, ops,
borrow::{
Cow, Borrow,
},
//collections::HashMap,
};
use regex::{
Regex, RegexBuilder,
};
use lazy_static::lazy_static;
/// Specifies locations and associated rules of these locations for path lookups when searching for plugins.
pub mod locations {
use super::*;
use bitflags::*;
/// Describes a path an its trust level.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
pub enum PathTrust<'a>
{
Trusted(&'a str),
Untrusted(&'a str),
}
bitflags! {
/// The rules a file in an unstructed lookup path must qualify for it to be considered for plugin candidacy.
pub struct UntrustedPathRules : u8
{
/// No restrictions on the file.
///
/// # Note
/// Must always be 0.
const UNRESTRICTED = 0;
/// Name ends in valid string from `LOOKUP_NAME_MATCHES_IN_UNTRUSTED`.
const NAME_MATCH = 1 << 0;
/// Disallow symlinked files.
const NON_SYMLINK = 1 << 1;
/// Disqualify executable-marked files.
const NON_EXEC = 1 << 2;
/// Disqualify world-writable files.
const NON_WORLD_WRITABLE = 1 << 3;
/// Only qualify root-owned files (can be unioned with other `_OWNED_ONLY` constants)
const ROOT_OWNED_ONLY = 1 << 4;
/// Only qualify user-owned files (can be unioned with other `_OWNED_ONLY` constants)
const USER_OWNED_ONLY = 1 << 5;
/// Owned by user or by root.
const USER_OR_ROOT_OWNED = Self::ROOT_OWNED_ONLY.bits | Self::USER_OWNED_ONLY.bits;
/// Disqualify files that are accessible at all by anyone other than the current user (or root, which can accesss everything.)
const USER_ACCESSIBLE_ONLY = 1 << 6;
/// Disallow *all* untrusted files.
///
/// # Note
/// Must be highest value.
const INACCESSIBLE = 1 << 7;
}
}
impl UntrustedPathRules
{
/// Contains function pointers for checking paths with every combination (**any**, not **all** in the mask) of rules in `UntrustedPathRules`.
///
/// Invalid functions will either:
/// * Panic if they are *never* supposed to be called in the pipeline (e.g `NAME_MATCH`.)
/// * Return false immediately.
pub const RULE_FUNCTION_CHECK_TABLE: [&'static (dyn Fn (&Path, &str) -> bool + 'static); 256] = Self::generate_rule_table();
/// Generate a table of 256 entries that which each single, or **OR**-combined mask entry of `UntrustedPathRules` has a corresponding path check function.
///
/// Invalid functions will either:
/// * Panic if they are *never* supposed to be called in the pipeline (e.g `NAME_MATCH`.)
/// * Return false immediately.
pub(super) const fn generate_rule_table() -> [&'static (dyn Fn (&Path, &str) -> bool + 'static); 256]
{
let mut rule_table = Self::generate_pruned_rule_table();
//TODO: Add in combinations via ORing its single-bit entry functions.
rule_table
}
/// Generate a table of 256 entries that which each **single** (not combinations of) entry of `UntrustedPathRules` has a corresponding path check function.
#[inline]
const fn generate_pruned_rule_table() -> [&'static (dyn Fn (&Path, &str) -> bool + 'static); 256]
{
type RuleCheckFunc<'a> = (dyn Fn(&Path, &str) -> bool + 'a);
#[inline]
const fn disallow(_: &Path, _: &str) -> bool { false }
let mut table: [&'static RuleCheckFunc; 256] = [&(disallow as fn(&Path, &str) -> bool); 256];
macro_rules! set {
($func:expr) => {
{
let func: &'static RuleCheckFunc<'static> = &($func);
func
}
};
($name:ident => $func:expr) => {
let bits = Self::$name.bits as usize;
// Fill in compositions based on previous entries into `table`.
let func: &'static RuleCheckFunc<'static> = set!($func);
/*let rv = {
let mut i: usize = 1;
//XXX: WHY doesn't this work???
while i < bits {
let prev = table[i];
table[bits | i] = set!(|path: &Path, name: &str| -> bool {
prev(path, name) && func(path, name)
});
i += 1;
}*/
table[bits] = func
//};
//rv
};
($name:ident as ! $panic_msg:literal) => {
#[inline(never)]
#[cold]
fn _panic_with_msg() -> ! {
panic!($panic_msg)
}
set!($name => |_, _| _panic_with_msg())
};
/*FUCK THIS, I HATE ITERATON IN MACROS< WHY DOES IT HAVE TO BE SO RECURSIVELY LEAKILY RETARDED AAAA(@ compose ($f_name:ident, $f_file:ident) via $operator:tt $ignore:tt; $first:ident, $second:ident $($rest:tt)*)
=> (table[Self::$first.bits() as usize]($f_name, $f_file) $operator set!(@ compose ($f_name, $f_file) via $operator &&; Self::$second.bits(), $($rest)*));
(@ compose ($f_name:ident, $f_file:ident) $(via $operator:tt)?; $($first:ident)? $(,)?)
=> ($(table[Self::$first.bits() as usize])?);
($name:ident $(; use $operator:tt)? => $($other:ident),+) => {
set!($name => |path, filename| {
set!(@ compose (path, filename) via $($operator)? &&; $($other),+)
});
}*/
}
/// In lookups, use process EUID instead of UID.
const USE_EFFECTIVE_UID: bool = true;
/// Get UID or **E**UID.
#[inline(always)]
fn get_uid<const E: bool>() -> u32
{
extern "C" {
fn getuid() -> u32; // uid_t
//XXX: Should we use process's UID or EUID? It seems we should use EUID.
fn geteuid() -> u32; // uid_t
}
// SAFETY: These are effectively pure.
unsafe {
if E {
geteuid()
} else {
getuid()
}
}
}
#[inline(always)]
fn check_uid<const CACHED: bool>(against: impl PartialEq<u32>) -> bool
{
//TODO: Allow these to be reset, somehow? Idk... Make them regular static atomics?
lazy_static! {
static ref USER_UID_CACHED: u32 = get_uid::<false>();
static ref USER_EUID_CACHED: u32 = get_uid::<true>();
}
against == if CACHED {
if USE_EFFECTIVE_UID {
*USER_EUID_CACHED
} else {
*USER_UID_CACHED
}
} else {
get_uid::<USE_EFFECTIVE_UID>()
}
}
set!(NAME_MATCH as ! "This should already have been checked");
set!(NON_SYMLINK => |path: &Path, _| !path.is_symlink());
set!(NON_EXEC => |path, _| {
use std::os::unix::fs::PermissionsExt as _;
use readable_perms::{
User,
Bit,
};
const EXEC_BIT: u32 =
User::Group.mode(Bit::Execute)
| User::Other.mode(Bit::Execute)
| User::Owner.mode(Bit::Execute);
path.metadata()
.map(|meta| meta.permissions().mode() & EXEC_BIT == 0) //TODO: XXX: Test this.
.unwrap_or(false)
});
set!(NON_WORLD_WRITABLE => |path, _| {
use readable_perms::{
PermissionsExt as _,
User,
Bit,
};
path.metadata()
.map(|meta| !meta.permissions().unix().has_mask(User::Other, Bit::Write))
.unwrap_or(false)
});
set!(ROOT_OWNED_ONLY => |path, _| {
use std::os::unix::fs::MetadataExt as _;
path.metadata()
.map(|meta| meta.uid() == 0)
.unwrap_or(false)
});
set!(USER_OWNED_ONLY => |path, _| {
use std::os::unix::fs::MetadataExt as _;
//let user_id = get_uid::<USE_EFFECTIVE_UID>();
path.metadata()
.map(move |meta| check_uid::<true>(meta.uid()))
.unwrap_or(false)
});
set!(USER_ACCESSIBLE_ONLY => |path, _| {
//Only Owner should be able to read, write, or execute.
use std::os::unix::fs::PermissionsExt as _;
use readable_perms::{
User,
Bit,
};
const BIT_ANY: Bit =
Bit::Read
.union(Bit::Write)
.union(Bit::Execute);
const OTHER_ACCESSORS: u32 =
User::Group.mode(BIT_ANY)
| User::Other.mode(BIT_ANY);
path.metadata()
.map(|meta| !meta.permissions().mode() & OTHER_ACCESSORS == 0) //XXX: Test this
.unwrap_or(false)
});
set!(USER_OR_ROOT_OWNED => |path, _| {
use std::os::unix::fs::MetadataExt as _;
//let user_id = get_uid::<USE_EFFECTIVE_UID>();
path.metadata()
.map(move |meta| match meta.uid() {
0 => true,
uid if check_uid::<true>(uid) => true,
_ => false
})
.unwrap_or(false)
});
//set!(USER_OR_ROOT_OWNED; use || => USER_OWNED_ONLY, ROOT_OWNED_ONLY);
//TODO: Composition of functions should be done via OR, how to insert all possible compositions into table? We get confusing error messages when we try inside `set!()`
table
}
}
impl Default for UntrustedPathRules
{
#[inline]
fn default() -> Self
{
Self::NAME_MATCH
}
}
impl<'a> AsRef<str> for PathTrust<'a>
{
#[inline(always)]
fn as_ref(&self) -> &str
{
match self {
Trusted(a) | Untrusted(a) => a
}
}
}
impl<'a> From<PathTrust<'a>> for &'a str
{
#[inline]
fn from(from: PathTrust<'a>) -> Self
{
from.into_str()
}
}
impl<'a> From<PathTrust<'a>> for (&'a str, bool)
{
#[inline]
fn from(from: PathTrust<'a>) -> Self
{
let trusted = from.is_trusted();
(from.into_str(), trusted)
}
}
// impl<'a> AsRef<Path> for PathTrust<'a>
// {
// #[inline]
// fn as_ref(&self) -> &Path
// {
// Path::new(AsRef::<str>::as_ref(self))
// }
// }
use PathTrust::*;
// The ordering of these variables is the order in which the plugin is looked-up in. The first match found is used.
/// Should paths be recursively travelled in lookups. (0 is for trusted paths, 1 is for untrusted)
pub static LOOKUP_PATH_RECURSION: (bool, bool) = (true, true);
/// Should symlinks be followed in a recursive lookup. (0 is for trusted paths, 1 is for untrusted)
pub static LOOKUP_PATH_FOLLOW_SYMLINKS: (bool, bool) = (true, false);
/// Trust plugin file lookups in non-contained `LOOKUP_PATH_HOME_BASE_NAME`s.
pub static LOOKUP_PATH_TRUST_HOME: bool = false;
/// Base paths from user home to look-up plugin name.
pub static LOOKUP_PATH_HOME_BASE_NAME: &[PathTrust] = &[
Trusted(".rngcli/plugins/"),
Untrusted(".rngcli/").map_trust(LOOKUP_PATH_TRUST_HOME),
Untrusted(".config/rngcli/").map_trust(LOOKUP_PATH_TRUST_HOME),
Untrusted(".config/").map_trust(LOOKUP_PATH_TRUST_HOME),
];
/// Extra valid lookup paths
pub static LOOKUP_PATH_EXTRA_PATHS: &[PathTrust] = &[
Trusted("/usr/share/rngcli/"),
Trusted("/usr/local/share/rngcli/")
];
/// `PATH`-like env-vars to look up a plugin's name.
pub static LOOKUP_PATH_ENV_VARS: &[PathTrust] = &[
Trusted("RNGCLI_PATH"),
Untrusted("PATH"),
];
/// Allow lookup inside the current working directory.
pub static LOOKUP_PATH_ALLOW_CWD: bool = true;
/// Files with these regex-matched names are considered in all lookup contexts (`Trusted` and `Untrusted`.)
pub static LOOKUP_NAME_MATCHES_IN_TRUSTED: &[&'static str] = &[
r"\.rngp\.so$",
r"\.rngp$",
r"\.so$",
];
/// Files with these regex-matched names are considered in `Untrusted` lookup contexts (if specified by `LOOKUP_UNTRUSTED_PATH_RULES`)
pub static LOOKUP_NAME_MATCHES_IN_UNTRUSTED: &[&'static str] = &[
r"\.rngp\.so$",
r"\.rngp$",
];
/// These rules must be met for lookups in untrusted paths.
pub static LOOKUP_UNTRUSTED_PATH_RULES: UntrustedPathRules =
UntrustedPathRules::NAME_MATCH
.union(UntrustedPathRules::NON_SYMLINK)
.union(UntrustedPathRules::NON_WORLD_WRITABLE);
impl<'a> PathTrust<'a>
{
#[inline(always)]
pub const fn into_str(self) -> &'a str
{
match self {
Trusted(a) | Untrusted(a) => a
}
}
#[inline(always)]
pub fn into_path(self) -> &'a Path
{
match self {
Trusted(a) | Untrusted(a) => Path::new(a)
}
}
#[inline(always)]
pub fn as_path(&self) -> &Path
{
Path::new(self.as_ref())
}
#[inline(always)]
pub const fn is_trusted(self) -> bool
{
if let Trusted(_) = self {
true
} else {
false
}
}
#[inline(always)]
pub const fn is_untrusted(self) -> bool
{
!self.is_trusted()
}
/// Map the trust level of this `PathTrust`.
#[inline(always)]
pub const fn map_trust(self, trust: bool) -> Self
{
if trust { Trusted(self.into_str()) }
else { Untrusted(self.into_str()) }
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct LookupVisitor<P: AsRef<Path>>(P);
#[derive(Debug)]
struct PathLookup {
name: String,
candidates: Vec<fs::DirEntry>,
trusted: bool,
}
impl PathLookup
{
#[inline]
pub fn check_valid_path<const TRUSTED: bool>(path: impl AsRef<Path>) -> bool
{
#[inline(always)]
fn regex_builder(pattern: impl AsRef<str>) -> Regex {
RegexBuilder::new(pattern.as_ref())
.case_insensitive(true)
.build().expect("Invalid regex in trusted pattern match array.")
}
/// Returns a function that returns `true` if any of the `matchers` match the input string.
#[inline]
const fn match_any<'a, I: 'a>(matchers: I) -> impl Fn(&str) -> bool + 'a
where I: IntoIterator<Item = &'a Regex> + Clone,
{
move |s: &str| -> bool {
let matchers = matchers.clone().into_iter();
for re in matchers {
if re.is_match(s) {
return true;
}
}
false
}
}
/// Validate a file in an untrusted lookup.
#[inline]
fn validate_file_untrusted(path: &Path, filename: &str) -> bool
{
// By this point, `filename` has already matched successfully.
// Also Note: `filename` is just the basename of `path` as an `&str` (aliasing address-space.)
use locations::UntrustedPathRules as Rule;
use locations::LOOKUP_UNTRUSTED_PATH_RULES as RULES;
let mut bit = Rule::INACCESSIBLE.bits();
// No rules
if bit == 0 /* `Rule::UNRESTRICTED` */ {
return true;
}
macro_rules! check {
($name:ident) => {
if RULES.contains(Rule::$name) {
Rule::RULE_FUNCTION_CHECK_TABLE[Rule::$name.bits() as usize](path, filename)
} else {
true
}
};
(try $($op:tt)? $name:ident) => {
if $($op)? check!($name) {
return false;
}
}
}
// XXX: Ugh, for now, this shit must be checked independently.
check!(try ! USER_OR_ROOT_OWNED);
// Check rest of rules in sequence. Hopefully this will be unrolled since the first `if` statement is all const-fn on a static constant.
while bit > 0
{
// SAFETY: We know `INACCESSIBLE` is the largest
if RULES.contains(unsafe { Rule::from_bits_unchecked(bit) }) {
if !Rule::RULE_FUNCTION_CHECK_TABLE[bit as usize](path, filename) {
return false;
}
}
bit >>= 1;
}
true
}
lazy_static! {
static ref TRUSTED_EXT_REGEX_MAP: Vec<Regex> =
locations::LOOKUP_NAME_MATCHES_IN_TRUSTED.into_iter().map(regex_builder).collect();
static ref UNTRUSTED_EXT_REGEX_MAP: Vec<Regex> =
locations::LOOKUP_NAME_MATCHES_IN_UNTRUSTED.into_iter().map(regex_builder).collect();
}
let path = path.as_ref();
// Check if the path:
// * Exists
// * Is not a directory
// NOTE: We don't use `.is_file()` here, because we want to support symlinks and pseudofiles in trusted contexts, which `is_file()` reports as false.
// TODO: XXX: Maybe in the future allow directories to be plugins; they would contain a manifest, and each `.so` file in the directory will be loaded in order specified by the directory's manifest.
if !path.exists() || path.is_dir() {
return false;
}
// Attempt to extract the filename, return false if it is invalid UTF8.
let filename = match path.file_name()
.and_then(|os_fn| os_fn.to_str())
{
Some(f) => f,
_ => return false,
};
// Get the name matcher and rule checker for the file.
let (matcher, checker) = if TRUSTED {
// Trusted path lookup
(match_any(TRUSTED_EXT_REGEX_MAP.iter()), None)//&NoChecker)
} else {
// Untrusted path lookup
let matcher = if locations::LOOKUP_UNTRUSTED_PATH_RULES
.contains(locations::UntrustedPathRules::NAME_MATCH)
{
// Name must match
match_any(UNTRUSTED_EXT_REGEX_MAP.iter())
} else {
// Name needn't match
match_any(TRUSTED_EXT_REGEX_MAP.iter())
};
(matcher, Some(move |path| {
validate_file_untrusted(path, filename)
}))
};
matcher(filename) // Check filename first
&& match checker {
Some(untrusted) => untrusted(path), // Check extra Untrusted path rules next
_ => true
}
}
}
impl<P: AsRef<Path>> LookupVisitor<P>
{
pub fn start_lookup(&self, trusted: bool, name: impl Into<String>) -> io::Result<PathLookup>
{
Ok(PathLookup {
name: name.into(),
trusted,
candidates: fs::read_dir(&self.0)?.collect::<io::Result<Vec<_>>>()?,
})
}
}
impl<'a, T: ToOwned<Owned = PathBuf> + 'a> From<(T, bool)> for PluginTrust<'a>
{
fn from((from, trust): (T, bool)) -> Self
{
(if trust {
Self::Trusted
} else {
Self::Untrusted
})(Cow::Owned(from.to_owned()))
.enforce_validity::<true>()
}
}
impl<'a> From<locations::PathTrust<'a>> for PluginTrust<'a>
{
#[inline]
fn from(from: locations::PathTrust<'a>) -> Self
{
match from {
locations::PathTrust::Trusted(a) => PluginTrust::Trusted(Cow::from(Path::new(a))),
locations::PathTrust::Untrusted(a) => PluginTrust::Untrusted(Cow::from(Path::new(a))),
}.enforce_validity::<true>()
}
}
/// Denoted a plugin and if it was loaded from a trusted path or not
#[derive(Debug, Clone, Eq, Hash, PartialOrd, Ord)]
pub enum PluginTrust<'a>
{
Trusted(Cow<'a, Path>),
Untrusted(Cow<'a, Path>)
}
impl<'a> ops::Deref for PluginTrust<'a>
{
type Target = Path;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_path()
}
}
impl<'a> Borrow<Path> for PluginTrust<'a>
{
#[inline(always)]
fn borrow(&self) -> &Path
{
match self {
Self::Trusted(a) | Self::Untrusted(a) => a.borrow()
}
}
}
impl<'a> AsRef<Path> for PluginTrust<'a>
{
#[inline]
fn as_ref(&self) -> &Path
{
self.borrow()
}
}
impl<'a> From<PluginTrust<'a>> for Cow<'a, Path>
{
#[inline]
fn from(from: PluginTrust<'a>) -> Self
{
match from {
PluginTrust::Trusted(a) | PluginTrust::Untrusted(a) => a
}
}
}
impl<'a> From<PluginTrust<'a>> for PathBuf
{
#[inline]
fn from(from: PluginTrust<'a>) -> Self {
let cow: Cow<Path> = from.into();
cow.into_owned()
}
}
impl<'a, T: AsRef<Path> + ?Sized> PartialEq<T> for PluginTrust<'a>
{
#[inline]
fn eq(&self, other: &T) -> bool
{
other.as_ref() == self.as_ref()
}
}
impl<'a> PluginTrust<'a>
{
/// Was this plugin loaded from a trusted path?
#[inline(always)]
pub fn is_trusted(&self) -> bool
{
if let Self::Trusted(_) = self {
true
} else {
false
}
}
/// Was this plugin loaded from an untrusted path?
#[inline]
pub fn is_untrusted(&self) -> bool
{
!self.is_trusted()
}
/// Get the `Path`, trusted or otherwise.
#[inline(always)]
fn as_path(&self) -> &Path
{
self.borrow()
}
/// Map this path to another.
///
/// # Panics
/// If the output of `func` is not a file-like object or does not exist.
#[inline]
pub fn map<F, T: Into<Cow<'a, Path>>>(self, func: F) -> Self
where F: FnOnce(Cow<'a, Path>) -> T
{
match self {
Self::Trusted(yes) => Self::Trusted(func(yes).into()),
Self::Untrusted(no) => Self::Untrusted(func(no).into())
}.enforce_validity::<false>()
}
#[inline(always)]
pub(super) fn is_valid(&self) -> bool
{
let path = self.as_path();
path.exists() && !path.is_dir() //XXX: Is this okay for validation for now? What about symlinked files?
}
#[inline(always)]
fn enforce_validity<const DEBUG: bool>(self) -> Self
{
if !DEBUG || cfg!(debug_assertions) {
#[inline(never)]
#[cold]
fn _panic_invalid(path: impl Into<PathBuf>) -> !
{
panic!("Illegal plugin path name: {:?}", path.into())
}
if !self.is_valid() {
_panic_invalid(self)
} else {
self
}
} else {
self
}
}
}
pub fn lookup_plugin_name(name: impl AsRef<str>) -> impl IntoIterator<Item = PluginTrust<'static>> + Send + 'static
{
todo!("Create a LookupVisitor(<path>) for each path in `locations`, then call `start_lookup(<trusted path>, name)` on each *in order* specified in `locations`");
None::<PluginTrust> //TODO: ^ see above
}
Loading…
Cancel
Save