use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
pub const PROVIDER_SCHEMA: &str = "text-mirror/provider@1";
pub const PROVIDER_ROLE_RASTER: &str = "raster-browser";
pub const PROVIDER_ROLE_ENCODER: &str = "raster-magick";
pub const PROVIDER_ROLES: [&str; 2] = [PROVIDER_ROLE_RASTER, PROVIDER_ROLE_ENCODER];
pub const PROVIDER_ADAPTER_VERSION: &str = "1.0.0";
pub const PROVIDER_GEOMETRY_TOLERANCE_PX: i64 = 1;
pub const PROVIDER_FLATTEN_ARGS: [&str; 8] = [
"-background",
"white",
"-alpha",
"remove",
"-alpha",
"off",
"-define",
"png:exclude-chunks=date,time",
];
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ProviderPin {
pub blake3: String,
pub version: String,
#[serde(default)]
pub closure_blake3: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderRole {
pub path: PathBuf,
#[serde(default)]
pub closure_roots: Vec<PathBuf>,
#[serde(default)]
pub jail_service_prefix: Option<String>,
#[serde(default)]
pub jail_temp_env: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawProviders {
#[serde(default)]
svg: Option<BTreeMap<String, ProviderRole>>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawProviderConfig {
schema: String,
#[serde(default)]
providers: Option<RawProviders>,
}
#[derive(Debug, Clone, Default)]
pub struct ProviderConfig {
svg: Option<SvgProvider>,
}
#[derive(Debug, Clone)]
pub struct SvgProvider {
pub raster: ProviderRole,
pub encoder: ProviderRole,
}
fn rules_error(message: String) -> Error {
Error::Rules {
name: "provider-config".to_string(),
message,
}
}
fn is_lower_hex_256(value: &str) -> bool {
value.len() == 64
&& value
.chars()
.all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c))
}
fn is_plain_version(value: &str) -> bool {
!value.is_empty()
&& value.len() <= 64
&& value
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_' | '+'))
}
pub fn is_service_namespace(value: &str) -> bool {
const MAX_SEGMENTS: usize = 8;
const MAX_SEGMENT: usize = 64;
if value.len() > 128 {
return false;
}
let Some(rest) = value.strip_prefix('^') else {
return false;
};
let Some(body) = rest.strip_suffix("\\.") else {
return false;
};
let segments: Vec<&str> = body.split("\\.").collect();
if segments.is_empty() || segments.len() > MAX_SEGMENTS {
return false;
}
segments.iter().all(|segment| {
!segment.is_empty()
&& segment.len() <= MAX_SEGMENT
&& segment
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
})
}
const SHARED_ROOTS: &[&str] = &[
"/",
"/Applications",
"/Library",
"/System",
"/Users",
"/bin",
"/etc",
"/home",
"/nix/store",
"/opt",
"/opt/homebrew",
"/opt/homebrew/Cellar",
"/opt/homebrew/bin",
"/opt/homebrew/lib",
"/opt/homebrew/opt",
"/private/tmp",
"/private/var",
"/sbin",
"/tmp",
"/usr",
"/usr/bin",
"/usr/lib",
"/usr/local",
"/usr/local/bin",
"/usr/local/lib",
"/usr/sbin",
"/usr/share",
"/var",
];
pub fn is_shared_root(path: &Path) -> bool {
if names_shared_root(path) {
return true;
}
match std::fs::canonicalize(path) {
Ok(real) => names_shared_root(&real),
Err(_) => false,
}
}
fn names_shared_root(path: &Path) -> bool {
let text = path.to_string_lossy();
let trimmed = text.trim_end_matches('/');
let normalized = if trimmed.is_empty() { "/" } else { trimmed };
if SHARED_ROOTS.contains(&normalized) {
return true;
}
path.components().count() < 3
}
pub fn is_normal_form(path: &Path) -> bool {
use std::path::Component;
path.is_absolute()
&& path
.components()
.all(|c| matches!(c, Component::RootDir | Component::Normal(_)))
}
pub fn is_symlink_entry(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|m| m.file_type().is_symlink())
.unwrap_or(false)
}
pub fn is_real_entry(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|m| m.file_type().is_dir() || m.file_type().is_file())
.unwrap_or(false)
}
pub fn canonical(path: &Path) -> Option<PathBuf> {
std::fs::canonicalize(path).ok()
}
pub fn lies_inside(real_path: &Path, real_root: &Path) -> bool {
real_path == real_root || real_path.starts_with(real_root)
}
pub fn is_env_name(value: &str) -> bool {
let mut chars = value.chars();
let Some(first) = chars.next() else {
return false;
};
(first.is_ascii_uppercase() || first == '_')
&& value.len() <= 64
&& chars.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
}
impl ProviderPin {
pub(crate) fn validate(&self, role: &str) -> std::result::Result<(), String> {
if !is_lower_hex_256(&self.blake3) {
return Err(format!(
"[image_ocr.svg_provider] role {role:?} blake3 is not a lowercase hex BLAKE3"
));
}
if !is_plain_version(&self.version) {
return Err(format!(
"[image_ocr.svg_provider] role {role:?} version is not a plain version string"
));
}
if let Some(digest) = &self.closure_blake3
&& !is_lower_hex_256(digest)
{
return Err(format!(
"[image_ocr.svg_provider] role {role:?} closure_blake3 is not a lowercase hex BLAKE3"
));
}
Ok(())
}
}
#[cfg(feature = "svg-provider")]
pub(crate) fn check_closure_is_pinned(
role: &str,
configured: &ProviderRole,
pin: Option<&ProviderPin>,
) -> std::result::Result<(), String> {
let pinned = pin.and_then(|pin| pin.closure_blake3.as_ref()).is_some();
let enumerated = !configured.closure_roots.is_empty();
match (enumerated, pinned) {
(true, false) => Err(format!(
"provider role {role:?} enumerates a closure that [image_ocr.svg_provider] does not pin"
)),
(false, true) => Err(format!(
"provider role {role:?} has a pinned closure digest but enumerates no closure to compute it over"
)),
_ => Ok(()),
}
}
pub(crate) fn validate_pins(
pins: &BTreeMap<String, ProviderPin>,
) -> std::result::Result<(), String> {
for role in pins.keys() {
if !PROVIDER_ROLES.contains(&role.as_str()) {
return Err(format!(
"[image_ocr.svg_provider] names unknown role {role:?}"
));
}
}
if !pins.is_empty() {
for role in PROVIDER_ROLES {
if !pins.contains_key(role) {
return Err(format!(
"[image_ocr.svg_provider] is missing role {role:?}: both roles form one complete provider"
));
}
}
}
for (role, pin) in pins {
pin.validate(role)?;
}
Ok(())
}
impl ProviderRole {
fn validated(&self, role: &str) -> Result<ProviderRole> {
if !self.path.is_absolute() {
return Err(rules_error(format!(
"provider role {role:?} needs an absolute path"
)));
}
if !is_normal_form(&self.path) {
return Err(rules_error(format!(
"provider role {role:?} path is not in normal form: write the real absolute path without . or .. components"
)));
}
let real_path = canonical(&self.path).ok_or_else(|| {
rules_error(format!(
"provider role {role:?} path {} cannot be resolved",
self.path.display()
))
})?;
if !std::fs::symlink_metadata(&real_path).is_ok_and(|m| m.file_type().is_file()) {
return Err(rules_error(format!(
"provider role {role:?} path {} is not a regular file",
self.path.display()
)));
}
let mut real_roots = Vec::with_capacity(self.closure_roots.len());
for root in &self.closure_roots {
if !root.is_absolute() {
return Err(rules_error(format!(
"provider role {role:?} needs absolute closure_roots"
)));
}
if !is_normal_form(root) {
return Err(rules_error(format!(
"provider role {role:?} closure_roots entry {} is not in normal form: write the real absolute path without . or .. components",
root.display()
)));
}
if is_symlink_entry(root) {
return Err(rules_error(format!(
"provider role {role:?} closure_roots entry {} is a symlink: enumerate the real path it names",
root.display()
)));
}
if is_shared_root(root) {
return Err(rules_error(format!(
"provider role {role:?} closure_roots entry {} is a shared root: enumerate the measured dependencies instead",
root.display()
)));
}
let real_root = canonical(root).ok_or_else(|| {
rules_error(format!(
"provider role {role:?} closure_roots entry {} cannot be resolved",
root.display()
))
})?;
if !is_real_entry(root) {
return Err(rules_error(format!(
"provider role {role:?} closure_roots entry {} is not a directory or a regular file",
root.display()
)));
}
if is_shared_root(&real_root) {
return Err(rules_error(format!(
"provider role {role:?} closure_roots entry {} is a shared root: enumerate the measured dependencies instead",
root.display()
)));
}
real_roots.push(real_root);
}
if !real_roots.is_empty() && !real_roots.iter().any(|root| lies_inside(&real_path, root)) {
return Err(rules_error(format!(
"provider role {role:?} executable lies outside every closure_roots entry"
)));
}
if let Some(prefix) = &self.jail_service_prefix
&& !is_service_namespace(prefix)
{
return Err(rules_error(format!(
"provider-parameter-invalid: provider role {role:?} jail_service_prefix is not an anchored dotted namespace"
)));
}
if let Some(name) = &self.jail_temp_env
&& !is_env_name(name)
{
return Err(rules_error(format!(
"provider-parameter-invalid: provider role {role:?} jail_temp_env is not a bounded identifier"
)));
}
Ok(ProviderRole {
path: real_path,
closure_roots: real_roots,
jail_service_prefix: self.jail_service_prefix.clone(),
jail_temp_env: self.jail_temp_env.clone(),
})
}
}
impl ProviderConfig {
pub fn empty() -> ProviderConfig {
ProviderConfig::default()
}
pub fn svg(&self) -> Option<&SvgProvider> {
self.svg.as_ref()
}
pub fn parse(text: &str) -> Result<ProviderConfig> {
let raw: RawProviderConfig =
toml::from_str(text).map_err(|e| rules_error(e.to_string()))?;
if raw.schema != PROVIDER_SCHEMA {
return Err(rules_error(format!(
"unrecognized provider schema {:?}, expected {PROVIDER_SCHEMA:?}",
raw.schema
)));
}
let Some(providers) = raw.providers else {
return Ok(ProviderConfig::empty());
};
let Some(roles) = providers.svg else {
return Ok(ProviderConfig::empty());
};
for name in roles.keys() {
if !PROVIDER_ROLES.contains(&name.as_str()) {
return Err(rules_error(format!(
"[providers.svg] names unknown role {name:?}"
)));
}
}
for role in PROVIDER_ROLES {
if !roles.contains_key(role) {
return Err(rules_error(format!(
"[providers.svg] is missing role {role:?}: both roles form one complete provider"
)));
}
}
let mut resolved = Vec::new();
for role in PROVIDER_ROLES {
resolved.push(roles[role].validated(role)?);
}
let encoder = resolved.pop().expect("both roles resolved");
let raster = resolved.pop().expect("both roles resolved");
Ok(ProviderConfig {
svg: Some(SvgProvider { raster, encoder }),
})
}
pub fn load(path: &Path) -> Result<ProviderConfig> {
let text = std::fs::read_to_string(path).map_err(|e| Error::io("read", path, e))?;
ProviderConfig::parse(&text)
}
pub fn digest_material(
pins: &BTreeMap<String, ProviderPin>,
jail: &ProviderJailIdentity,
) -> Option<String> {
let mut material = String::from("provider=svg\n");
for label in PROVIDER_ROLES {
let pin = pins.get(label)?;
material.push_str(&format!("role={label}\n"));
material.push_str(&format!("blake3={}\n", pin.blake3));
material.push_str(&format!("version={}\n", pin.version));
if let Some(digest) = &pin.closure_blake3 {
material.push_str(&format!("closure={digest}\n"));
}
}
material.push_str(&format!("adapter={PROVIDER_ADAPTER_VERSION}\n"));
material.push_str(&format!(
"geometry-tolerance-px={PROVIDER_GEOMETRY_TOLERANCE_PX}\n"
));
material.push_str(&format!("flatten={}\n", PROVIDER_FLATTEN_ARGS.join(" ")));
material.push_str(&format!(
"area-cap={}\n",
crate::convert::IMAGE_OCR_MAX_AREA_PX
));
material.push_str(&format!(
"template={}\n",
jail.template_blake3.as_deref().unwrap_or("none")
));
material.push_str(&format!(
"service-prefix={}\n",
jail.service_prefix.as_deref().unwrap_or_default()
));
material.push_str(&format!(
"temp-env={}\n",
jail.temp_env.as_deref().unwrap_or_default()
));
Some(material)
}
pub fn version_suffix(
pins: &BTreeMap<String, ProviderPin>,
jail: &ProviderJailIdentity,
) -> Option<String> {
let material = Self::digest_material(pins, jail)?;
let digest = crate::hash::hash_bytes(material.as_bytes());
Some(format!("svg.{}", &digest[..12]))
}
pub fn jail_identity(&self) -> ProviderJailIdentity {
let raster = self.svg.as_ref().map(|svg| &svg.raster);
ProviderJailIdentity {
template_blake3: crate::runner_template_blake3(),
service_prefix: raster.and_then(|role| role.jail_service_prefix.clone()),
temp_env: raster.and_then(|role| role.jail_temp_env.clone()),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ProviderJailIdentity {
pub template_blake3: Option<String>,
pub service_prefix: Option<String>,
pub temp_env: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
fn config(body: &str) -> String {
format!("schema = \"{PROVIDER_SCHEMA}\"\n{body}")
}
struct Deploy {
_dir: tempfile::TempDir,
root: PathBuf,
}
impl Deploy {
fn new() -> Deploy {
let dir = tempfile::tempdir().unwrap();
let root = dir.path().canonicalize().unwrap().join("deploy");
for rel in [
"raster/bin/raster",
"encoder/bin/encoder",
"dependency/lib/dep.dylib",
"elsewhere/bin/raster",
] {
let path = root.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, b"program").unwrap();
}
Deploy { _dir: dir, root }
}
fn at(&self, rel: &str) -> String {
self.root.join(rel).display().to_string()
}
fn raster_path_line(&self) -> String {
format!("path = \"{}\"", self.at("raster/bin/raster"))
}
fn raster_roots_line(&self) -> String {
format!("closure_roots = [\"{}\"]", self.at("raster"))
}
fn encoder_path_line(&self) -> String {
format!("path = \"{}\"", self.at("encoder/bin/encoder"))
}
fn encoder_roots_line(&self) -> String {
format!(
"closure_roots = [\"{}\", \"{}\"]",
self.at("encoder"),
self.at("dependency/lib/dep.dylib")
)
}
fn complete(&self) -> String {
config(&format!(
"\n[providers.svg.\"{PROVIDER_ROLE_RASTER}\"]\n{}\n{}\n\
jail_service_prefix = \"^ex\\\\.ample\\\\.Product\\\\.\"\n\
jail_temp_env = \"EXAMPLE_TMPDIR\"\n\n\
[providers.svg.\"{PROVIDER_ROLE_ENCODER}\"]\n{}\n{}\n",
self.raster_path_line(),
self.raster_roots_line(),
self.encoder_path_line(),
self.encoder_roots_line(),
))
}
}
fn pins() -> BTreeMap<String, ProviderPin> {
let mut pins = BTreeMap::new();
pins.insert(
PROVIDER_ROLE_RASTER.to_string(),
ProviderPin {
blake3: "a".repeat(64),
version: "151.0.7922.174".to_string(),
closure_blake3: Some("b".repeat(64)),
},
);
pins.insert(
PROVIDER_ROLE_ENCODER.to_string(),
ProviderPin {
blake3: "c".repeat(64),
version: "7.1.2-29".to_string(),
closure_blake3: None,
},
);
pins
}
#[test]
fn a_complete_configuration_resolves_both_roles() {
let deploy = Deploy::new();
let parsed = ProviderConfig::parse(&deploy.complete()).unwrap();
let svg = parsed.svg().expect("both roles present");
assert_eq!(svg.raster.path, deploy.root.join("raster/bin/raster"));
assert_eq!(
svg.raster.jail_service_prefix.as_deref(),
Some(r"^ex\.ample\.Product\.")
);
assert_eq!(svg.raster.jail_temp_env.as_deref(), Some("EXAMPLE_TMPDIR"));
assert_eq!(
svg.encoder.closure_roots,
vec![
deploy.root.join("encoder"),
deploy.root.join("dependency/lib/dep.dylib")
]
);
assert!(svg.encoder.jail_service_prefix.is_none());
}
#[test]
fn an_empty_or_provider_free_configuration_carries_no_provider() {
assert!(ProviderConfig::empty().svg().is_none());
assert!(ProviderConfig::parse(&config("")).unwrap().svg().is_none());
assert!(
ProviderConfig::parse(&config("[providers]\n"))
.unwrap()
.svg()
.is_none()
);
}
#[test]
fn an_incomplete_provider_block_is_a_configuration_error() {
let one_role = config(&format!(
"[providers.svg.\"{PROVIDER_ROLE_RASTER}\"]\npath = \"/deploy/raster\"\n"
));
let error = ProviderConfig::parse(&one_role).unwrap_err();
assert!(error.to_string().contains("missing role"), "{error}");
}
#[test]
fn the_schema_carries_no_other_provider_and_no_other_role() {
let heic = config("[providers.heic.\"raster-browser\"]\npath = \"/x\"\n");
let error = ProviderConfig::parse(&heic).unwrap_err();
assert!(error.to_string().contains("unknown field"), "{error}");
let extra = config("[providers.svg.\"raster-transcoder\"]\npath = \"/x\"\n");
let error = ProviderConfig::parse(&extra).unwrap_err();
assert!(error.to_string().contains("unknown role"), "{error}");
let pinned = config(&format!(
"[providers.svg.\"{PROVIDER_ROLE_RASTER}\"]\npath = \"/x\"\nblake3 = \"{}\"\n",
"a".repeat(64)
));
assert!(ProviderConfig::parse(&pinned).is_err());
}
#[test]
fn a_wrong_schema_and_a_malformed_file_are_configuration_errors() {
let error = ProviderConfig::parse("schema = \"text-mirror/provider@2\"\n").unwrap_err();
assert!(error.to_string().contains("provider schema"), "{error}");
assert!(ProviderConfig::parse("not = toml = at all").is_err());
assert!(ProviderConfig::parse("").is_err());
}
#[test]
fn paths_are_validated_before_a_run_starts() {
let deploy = Deploy::new();
let relative = deploy
.complete()
.replace(&deploy.raster_path_line(), "path = \"relative/raster\"");
assert!(
ProviderConfig::parse(&relative)
.unwrap_err()
.to_string()
.contains("absolute path")
);
let relative_closure = deploy.complete().replace(
&deploy.encoder_roots_line(),
"closure_roots = [\"relative/lib\"]",
);
assert!(
ProviderConfig::parse(&relative_closure)
.unwrap_err()
.to_string()
.contains("closure_roots")
);
for shared in [
"/",
"/opt",
"/opt/homebrew",
"/opt/homebrew/Cellar",
"/opt/homebrew/opt",
"/usr/lib",
"/usr/local/lib",
"/Applications",
"/Library",
"/System",
"/bin",
"/opt/homebrew/Cellar/",
] {
let text = deploy.complete().replace(
&deploy.encoder_roots_line(),
&format!("closure_roots = [\"{shared}\"]"),
);
let error = ProviderConfig::parse(&text).unwrap_err().to_string();
assert!(error.contains("shared root"), "{shared}: {error}");
}
for named in [
"/opt/homebrew/Cellar/example/1.2.3",
"/opt/homebrew/Cellar/example/1.2.3/lib/libexample.dylib",
"/Applications/Example.app",
] {
assert!(!is_shared_root(Path::new(named)), "{named}");
}
let missing = deploy.complete().replace(
&deploy.raster_path_line(),
&format!("path = \"{}\"", deploy.at("raster/bin/absent")),
);
let error = ProviderConfig::parse(&missing).unwrap_err().to_string();
assert!(error.contains("cannot be resolved"), "{error}");
let missing_entry = deploy.complete().replace(
&deploy.encoder_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("encoder-absent")),
);
let error = ProviderConfig::parse(&missing_entry)
.unwrap_err()
.to_string();
assert!(error.contains("cannot be resolved"), "{error}");
let parsed = ProviderConfig::parse(&deploy.complete()).unwrap();
let svg = parsed.svg().unwrap();
assert!(
check_closure_is_pinned(
PROVIDER_ROLE_RASTER,
&svg.raster,
pins().get(PROVIDER_ROLE_RASTER)
)
.is_ok()
);
let unpinned = ProviderPin {
blake3: "a".repeat(64),
version: "1".to_string(),
closure_blake3: None,
};
let error = check_closure_is_pinned(PROVIDER_ROLE_RASTER, &svg.raster, Some(&unpinned))
.unwrap_err();
assert!(error.contains("does not pin"), "{error}");
assert!(check_closure_is_pinned(PROVIDER_ROLE_RASTER, &svg.raster, None).is_err());
}
#[test]
fn the_service_namespace_grammar_is_closed_and_anchored() {
for good in [
r"^a\.",
r"^com\.example\.",
r"^com\.example\.Product\.",
r"^org-name\.some_thing\.X9\.",
] {
assert!(is_service_namespace(good), "{good}");
}
for bad in [
"",
"^",
r"^\.",
r"com\.example\.",
r"^com\.example",
r"^com.example\.",
r"^(org\.a|com\.b)\.",
r"^com\.[a-z]+\.",
r"^com\..*\.",
r"^com\.\.example\.",
r"^com\.exa mple\.",
r#"^com\.ex"ample\."#,
r".*",
"^$",
] {
assert!(!is_service_namespace(bad), "{bad:?}");
}
let long = format!("^{}\\.", "a".repeat(130));
assert!(!is_service_namespace(&long));
let many = format!("^{}\\.", ["a"; 9].join("\\."));
assert!(!is_service_namespace(&many));
let eight = format!("^{}\\.", ["a"; 8].join("\\."));
assert!(is_service_namespace(&eight));
}
#[test]
fn the_temp_variable_name_is_a_bounded_identifier() {
for good in ["X", "_X", "MY_TMPDIR", "A1_B2", &"A".repeat(64)] {
assert!(is_env_name(good), "{good}");
}
for bad in [
"",
"lower",
"1ABC",
"HAS-DASH",
"HAS SPACE",
"HAS=EQ",
&"A".repeat(65),
] {
assert!(!is_env_name(bad), "{bad:?}");
}
}
#[test]
fn an_out_of_shape_parameter_is_refused_by_name_before_any_run() {
let deploy = Deploy::new();
let free = deploy.complete().replace(
r#"jail_service_prefix = "^ex\\.ample\\.Product\\.""#,
r#"jail_service_prefix = "^(com\\.a|org\\.b)\\.""#,
);
let error = ProviderConfig::parse(&free).unwrap_err().to_string();
assert!(error.contains("provider-parameter-invalid"), "{error}");
assert!(error.contains("jail_service_prefix"), "{error}");
let unanchored = deploy.complete().replace(
r#"jail_service_prefix = "^ex\\.ample\\.Product\\.""#,
r#"jail_service_prefix = "ex\\.ample\\.""#,
);
assert!(ProviderConfig::parse(&unanchored).is_err());
let long_name = deploy.complete().replace(
"jail_temp_env = \"EXAMPLE_TMPDIR\"",
&format!("jail_temp_env = \"{}\"", "T".repeat(65)),
);
let error = ProviderConfig::parse(&long_name).unwrap_err().to_string();
assert!(error.contains("provider-parameter-invalid"), "{error}");
assert!(error.contains("jail_temp_env"), "{error}");
let lower = deploy.complete().replace(
"jail_temp_env = \"EXAMPLE_TMPDIR\"",
"jail_temp_env = \"lowercase\"",
);
assert!(ProviderConfig::parse(&lower).is_err());
}
#[test]
fn the_rules_pins_are_validated_as_a_pair() {
assert!(validate_pins(&BTreeMap::new()).is_ok());
assert!(validate_pins(&pins()).is_ok());
let mut one = pins();
one.remove(PROVIDER_ROLE_ENCODER);
assert!(validate_pins(&one).unwrap_err().contains("missing role"));
let mut extra = pins();
extra.insert(
"raster-transcoder".to_string(),
ProviderPin {
blake3: "a".repeat(64),
version: "1".to_string(),
closure_blake3: None,
},
);
assert!(validate_pins(&extra).unwrap_err().contains("unknown role"));
let mut bad_hash = pins();
bad_hash.get_mut(PROVIDER_ROLE_RASTER).unwrap().blake3 = "SHOUTING".to_string();
assert!(validate_pins(&bad_hash).unwrap_err().contains("BLAKE3"));
let mut bad_version = pins();
bad_version.get_mut(PROVIDER_ROLE_ENCODER).unwrap().version =
"151 (build 7922)".to_string();
assert!(validate_pins(&bad_version).unwrap_err().contains("version"));
let mut bad_closure = pins();
bad_closure
.get_mut(PROVIDER_ROLE_RASTER)
.unwrap()
.closure_blake3 = Some("zz".to_string());
assert!(
validate_pins(&bad_closure)
.unwrap_err()
.contains("closure_blake3")
);
}
fn identity() -> ProviderJailIdentity {
ProviderJailIdentity {
template_blake3: Some("t".repeat(64)),
service_prefix: Some(r"^ex\.ample\.".to_string()),
temp_env: Some("EXAMPLE_TMPDIR".to_string()),
}
}
#[test]
fn the_effective_version_suffix_covers_identity_and_excludes_paths() {
let suffix = ProviderConfig::version_suffix(&pins(), &identity()).expect("complete pins");
assert!(suffix.starts_with("svg."), "{suffix}");
assert_eq!(suffix.len(), "svg.".len() + 12, "{suffix}");
let mut one = pins();
one.remove(PROVIDER_ROLE_ENCODER);
assert!(ProviderConfig::version_suffix(&one, &identity()).is_none());
for mutate in [
|p: &mut BTreeMap<String, ProviderPin>| {
p.get_mut(PROVIDER_ROLE_RASTER).unwrap().blake3 = "1".repeat(64)
},
|p: &mut BTreeMap<String, ProviderPin>| {
p.get_mut(PROVIDER_ROLE_RASTER).unwrap().closure_blake3 = Some("2".repeat(64))
},
|p: &mut BTreeMap<String, ProviderPin>| {
p.get_mut(PROVIDER_ROLE_ENCODER).unwrap().blake3 = "3".repeat(64)
},
|p: &mut BTreeMap<String, ProviderPin>| {
p.get_mut(PROVIDER_ROLE_ENCODER).unwrap().version = "7.1.2-30".to_string()
},
] {
let mut changed = pins();
mutate(&mut changed);
assert_ne!(
ProviderConfig::version_suffix(&changed, &identity()),
Some(suffix.clone())
);
}
let material = ProviderConfig::digest_material(&pins(), &identity()).unwrap();
assert!(material.contains(PROVIDER_ROLE_RASTER));
assert!(material.contains(PROVIDER_ROLE_ENCODER));
assert!(material.contains(PROVIDER_ADAPTER_VERSION));
assert!(material.contains("png:exclude-chunks=date,time"));
assert!(material.contains("geometry-tolerance-px=1"));
assert!(material.contains(&"t".repeat(64)));
assert!(material.contains("EXAMPLE_TMPDIR"));
assert!(!material.contains('/'), "{material}");
}
#[test]
fn the_jail_identity_is_part_of_the_effective_version() {
let base = ProviderConfig::version_suffix(&pins(), &identity()).unwrap();
let mut other_namespace = identity();
other_namespace.service_prefix = Some(r"^an\.other\.".to_string());
assert_ne!(
ProviderConfig::version_suffix(&pins(), &other_namespace),
Some(base.clone())
);
let mut other_variable = identity();
other_variable.temp_env = Some("OTHER_TMPDIR".to_string());
assert_ne!(
ProviderConfig::version_suffix(&pins(), &other_variable),
Some(base.clone())
);
let mut edited_template = identity();
edited_template.template_blake3 = Some("u".repeat(64));
assert_ne!(
ProviderConfig::version_suffix(&pins(), &edited_template),
Some(base.clone())
);
let mut no_parameters = identity();
no_parameters.service_prefix = None;
no_parameters.temp_env = None;
assert_ne!(
ProviderConfig::version_suffix(&pins(), &no_parameters),
Some(base.clone())
);
assert_eq!(
ProviderConfig::version_suffix(&pins(), &identity()),
Some(base)
);
let deploy = Deploy::new();
let parsed = ProviderConfig::parse(&deploy.complete()).unwrap();
let identity = parsed.jail_identity();
assert_eq!(
identity.service_prefix.as_deref(),
Some(r"^ex\.ample\.Product\.")
);
assert_eq!(identity.temp_env.as_deref(), Some("EXAMPLE_TMPDIR"));
}
#[test]
fn an_executable_outside_its_own_closure_is_refused() {
let deploy = Deploy::new();
let outside = deploy.complete().replace(
&deploy.raster_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("elsewhere")),
);
let error = ProviderConfig::parse(&outside).unwrap_err().to_string();
assert!(error.contains("outside every closure_roots"), "{error}");
let as_entry = deploy.complete().replace(
&deploy.raster_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("raster/bin/raster")),
);
assert!(ProviderConfig::parse(&as_entry).is_ok());
let among = deploy.complete().replace(
&deploy.raster_roots_line(),
&format!(
"closure_roots = [\"{}\", \"{}\"]",
deploy.at("elsewhere"),
deploy.at("raster")
),
);
assert!(ProviderConfig::parse(&among).is_ok());
let none = deploy
.complete()
.replace(&format!("{}\n", deploy.raster_roots_line()), "");
assert!(ProviderConfig::parse(&none).is_ok());
}
#[test]
fn containment_is_decided_on_resolved_paths() {
let deploy = Deploy::new();
let dotted = deploy.complete().replace(
&deploy.raster_path_line(),
&format!("path = \"{}\"", deploy.at("raster/../elsewhere/bin/raster")),
);
let error = ProviderConfig::parse(&dotted).unwrap_err().to_string();
assert!(error.contains("not in normal form"), "{error}");
std::os::unix::fs::symlink(
deploy.root.join("elsewhere"),
deploy.root.join("raster/link"),
)
.unwrap();
let escaping = deploy.complete().replace(
&deploy.raster_path_line(),
&format!("path = \"{}\"", deploy.at("raster/link/bin/raster")),
);
let error = ProviderConfig::parse(&escaping).unwrap_err().to_string();
assert!(error.contains("outside every closure_roots"), "{error}");
std::os::unix::fs::symlink(deploy.root.join("raster"), deploy.root.join("alias")).unwrap();
let entering = deploy.complete().replace(
&deploy.raster_path_line(),
&format!("path = \"{}\"", deploy.at("alias/bin/raster")),
);
let parsed = ProviderConfig::parse(&entering).unwrap();
assert_eq!(
parsed.svg().unwrap().raster.path,
deploy.root.join("raster/bin/raster")
);
let alias = deploy.complete().replace(
&deploy.encoder_roots_line(),
"closure_roots = [\"/private/tmp/../tmp\"]",
);
let error = ProviderConfig::parse(&alias).unwrap_err().to_string();
assert!(error.contains("not in normal form"), "{error}");
std::os::unix::fs::symlink("/usr/lib", deploy.root.join("store")).unwrap();
let linked = deploy.complete().replace(
&deploy.encoder_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("store")),
);
let error = ProviderConfig::parse(&linked).unwrap_err().to_string();
assert!(error.contains("is a symlink"), "{error}");
let real_link = deploy.complete().replace(
&deploy.raster_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("alias")),
);
let error = ProviderConfig::parse(&real_link).unwrap_err().to_string();
assert!(error.contains("is a symlink"), "{error}");
std::os::unix::fs::symlink("/usr", deploy.root.join("usrlink")).unwrap();
assert!(!is_symlink_entry(&deploy.root.join("usrlink/lib")));
assert!(is_real_entry(&deploy.root.join("usrlink/lib")));
let carried = deploy.complete().replace(
&deploy.encoder_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("usrlink/lib")),
);
let error = ProviderConfig::parse(&carried).unwrap_err().to_string();
assert!(error.contains("shared root"), "{error}");
let socket = deploy.root.join("sock");
let _listener = std::os::unix::net::UnixListener::bind(&socket).unwrap();
assert!(!is_real_entry(&socket));
let special_entry = deploy.complete().replace(
&deploy.encoder_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("sock")),
);
let error = ProviderConfig::parse(&special_entry)
.unwrap_err()
.to_string();
assert!(
error.contains("not a directory or a regular file"),
"{error}"
);
let special_executable = deploy.complete().replace(
&deploy.raster_path_line(),
&format!("path = \"{}\"", deploy.at("sock")),
);
let error = ProviderConfig::parse(&special_executable)
.unwrap_err()
.to_string();
assert!(error.contains("not a regular file"), "{error}");
let fifo = deploy.root.join("fifo");
assert!(
std::process::Command::new("/usr/bin/mkfifo")
.arg(&fifo)
.status()
.unwrap()
.success()
);
assert!(!is_real_entry(&fifo));
let fifo_entry = deploy.complete().replace(
&deploy.encoder_roots_line(),
&format!("closure_roots = [\"{}\"]", deploy.at("fifo")),
);
let error = ProviderConfig::parse(&fifo_entry).unwrap_err().to_string();
assert!(
error.contains("not a directory or a regular file"),
"{error}"
);
}
}