use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use crate::convert::provider::{
PROVIDER_FLATTEN_ARGS, PROVIDER_GEOMETRY_TOLERANCE_PX, PROVIDER_ROLE_ENCODER,
PROVIDER_ROLE_RASTER,
};
use crate::runner::protocol::bodies::{SvgRasterOk, SvgRasterRequest, SvgRoleEntry};
const GEOMETRY_SCAN_BYTES: usize = 64 * 1024;
const MAX_WINDOW_PX: i64 = 20_000;
pub(crate) const JAIL_SOURCE: &str = "doc.svg";
pub(crate) const JAIL_RAW: &str = "raw.png";
pub(crate) const JAIL_FLAT: &str = "flat.png";
#[derive(Debug)]
pub(crate) struct SvgRasterError {
pub code: &'static str,
pub message: String,
}
impl SvgRasterError {
fn new(code: &'static str, message: impl Into<String>) -> SvgRasterError {
SvgRasterError {
code,
message: message.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct Geometry {
pub(crate) width: f64,
pub(crate) height: f64,
}
impl Geometry {
fn window(self) -> Result<(i64, i64), SvgRasterError> {
let round = |value: f64| -> Result<i64, SvgRasterError> {
let rounded = value.round();
if !rounded.is_finite() || rounded < 1.0 || rounded > MAX_WINDOW_PX as f64 {
return Err(SvgRasterError::new(
"raster-geometry-mismatch",
"the declared source geometry is outside the renderable range",
));
}
Ok(rounded as i64)
};
Ok((round(self.width)?, round(self.height)?))
}
}
fn length_to_px(raw: &str) -> Option<f64> {
let text = raw.trim();
let (number, unit) = match text.find(|c: char| c.is_ascii_alphabetic() || c == '%') {
Some(index) => (&text[..index], text[index..].trim()),
None => (text, ""),
};
let value: f64 = number.trim().parse().ok()?;
if !value.is_finite() || value <= 0.0 {
return None;
}
let ratio = match unit.to_ascii_lowercase().as_str() {
"" | "px" => 1.0,
"pt" => 96.0 / 72.0,
"pc" => 16.0,
"in" => 96.0,
"cm" => 96.0 / 2.54,
"mm" => 96.0 / 25.4,
"q" => 96.0 / 101.6,
_ => return None,
};
Some(value * ratio)
}
pub(crate) fn parse_geometry(source: &[u8]) -> Result<Geometry, SvgRasterError> {
use quick_xml::events::Event;
let unresolved = || {
SvgRasterError::new(
"raster-geometry-mismatch",
"the source declares no resolvable root geometry",
)
};
let head = &source[..source.len().min(GEOMETRY_SCAN_BYTES)];
let mut reader = quick_xml::Reader::from_reader(head);
let mut buffer = Vec::new();
let mut width = None;
let mut height = None;
let mut view_box = None;
loop {
match reader.read_event_into(&mut buffer) {
Ok(Event::Start(start)) | Ok(Event::Empty(start)) => {
if start.local_name().as_ref() != b"svg" {
return Err(unresolved());
}
for attribute in start.attributes().flatten() {
let key = attribute.key.local_name();
let value = match std::str::from_utf8(&attribute.value) {
Ok(value) => value.to_string(),
Err(_) => continue,
};
match key.as_ref() {
b"width" => width = Some(value),
b"height" => height = Some(value),
b"viewBox" => view_box = Some(value),
_ => {}
}
}
break;
}
Ok(Event::Eof) | Err(_) => return Err(unresolved()),
Ok(_) => {}
}
buffer.clear();
}
if let (Some(width), Some(height)) = (&width, &height)
&& let (Some(width), Some(height)) = (length_to_px(width), length_to_px(height))
{
return Ok(Geometry { width, height });
}
let view_box = view_box.ok_or_else(unresolved)?;
let numbers: Vec<f64> = view_box
.split(|c: char| c.is_ascii_whitespace() || c == ',')
.filter(|part| !part.is_empty())
.filter_map(|part| part.parse::<f64>().ok())
.collect();
if numbers.len() != 4 {
return Err(unresolved());
}
let (width, height) = (numbers[2], numbers[3]);
if !width.is_finite() || !height.is_finite() || width <= 0.0 || height <= 0.0 {
return Err(unresolved());
}
Ok(Geometry { width, height })
}
pub(crate) fn assert_geometry(
source: Geometry,
out_width: u32,
out_height: u32,
) -> Result<(), SvgRasterError> {
let mismatch = || {
SvgRasterError::new(
"raster-geometry-mismatch",
"the rendered raster does not preserve the declared source aspect",
)
};
if out_width == 0 || out_height == 0 {
return Err(mismatch());
}
let (ow, oh) = (f64::from(out_width), f64::from(out_height));
let expected_height = (ow * source.height / source.width).round();
let expected_width = (oh * source.width / source.height).round();
if !expected_height.is_finite() || !expected_width.is_finite() {
return Err(mismatch());
}
let tolerance = PROVIDER_GEOMETRY_TOLERANCE_PX as f64;
if (expected_height - oh).abs() > tolerance || (expected_width - ow).abs() > tolerance {
return Err(mismatch());
}
Ok(())
}
pub(crate) fn raster_argv(width: i64, height: i64, output: &Path, input: &Path) -> Vec<String> {
vec![
"--headless=new".to_string(),
"--disable-gpu".to_string(),
"--hide-scrollbars".to_string(),
format!("--window-size={width},{height}"),
"--force-device-scale-factor=1".to_string(),
"--disable-background-networking".to_string(),
"--disable-component-update".to_string(),
"--disable-domain-reliability".to_string(),
"--disable-sync".to_string(),
"--no-first-run".to_string(),
"--no-default-browser-check".to_string(),
"--metrics-recording-only".to_string(),
"--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE localhost".to_string(),
format!("--screenshot={}", output.display()),
format!("file://{}", input.display()),
"--no-sandbox".to_string(),
]
}
pub(crate) fn flatten_argv(input: &Path, output: &Path) -> Vec<String> {
let mut argv = vec![input.display().to_string()];
argv.extend(PROVIDER_FLATTEN_ARGS.iter().map(|arg| (*arg).to_string()));
argv.push(output.display().to_string());
argv
}
pub(crate) fn closure_digest(roots: &[PathBuf], launcher: &Path) -> Result<String, SvgRasterError> {
let mut digests: Vec<String> = Vec::new();
for root in roots {
digests.push(entry_digest(root, launcher)?);
}
digests.sort();
let mut hasher = blake3::Hasher::new();
for digest in digests {
hasher.update(digest.as_bytes());
hasher.update(b"\n");
}
Ok(hasher.finalize().to_hex().to_string())
}
fn entry_digest(root: &Path, launcher: &Path) -> Result<String, SvgRasterError> {
const MAX_CLOSURE_FILES: usize = 4096;
let unreadable = |detail: &'static str| SvgRasterError::new("rasterizer-hash-drift", detail);
let metadata = std::fs::symlink_metadata(root)
.map_err(|_| unreadable("a runtime closure entry cannot be read"))?;
let mut rows: Vec<(Vec<u8>, ClosureMember)> = Vec::new();
let name = root
.file_name()
.ok_or_else(|| unreadable("a runtime closure entry has no name"))?;
if metadata.file_type().is_symlink() {
return Err(unreadable("a runtime closure entry is a symlink"));
} else if metadata.is_file() {
rows.push((
name.as_encoded_bytes().to_vec(),
ClosureMember::File(root.to_path_buf()),
));
} else {
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = std::fs::read_dir(&dir)
.map_err(|_| unreadable("the runtime closure cannot be enumerated"))?;
for entry in entries {
let entry =
entry.map_err(|_| unreadable("the runtime closure cannot be enumerated"))?;
let path = entry.path();
let metadata = std::fs::symlink_metadata(&path)
.map_err(|_| unreadable("the runtime closure cannot be enumerated"))?;
let kind = metadata.file_type();
if kind.is_dir() {
stack.push(path);
continue;
}
let member = if kind.is_symlink() {
let target = std::fs::read_link(&path)
.map_err(|_| unreadable("a runtime closure link cannot be read"))?;
ClosureMember::Link(target.as_os_str().as_encoded_bytes().to_vec())
} else if kind.is_file() {
if path == launcher {
continue;
}
ClosureMember::File(path.clone())
} else {
continue;
};
if rows.len() >= MAX_CLOSURE_FILES {
return Err(unreadable(
"a runtime closure entry holds more files than expected",
));
}
let relative = path
.strip_prefix(root)
.unwrap_or(path.as_path())
.as_os_str()
.as_encoded_bytes()
.to_vec();
rows.push((relative, member));
}
}
}
rows.sort();
let mut hasher = blake3::Hasher::new();
for (relative, member) in rows {
hasher.update(&relative);
hasher.update(b"\n");
match member {
ClosureMember::File(path) => {
let hash = crate::hash::hash_file(&path)
.map_err(|_| unreadable("a runtime closure member cannot be read"))?;
hasher.update(hash.as_bytes());
}
ClosureMember::Link(target) => {
hasher.update(b"link:");
hasher.update(&target);
}
}
hasher.update(b"\n");
}
Ok(hasher.finalize().to_hex().to_string())
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum ClosureMember {
File(PathBuf),
Link(Vec<u8>),
}
fn verify_role(entry: &SvgRoleEntry) -> Result<PathBuf, SvgRasterError> {
let role = entry.role.as_str();
let path = PathBuf::from(&entry.path);
let metadata = std::fs::symlink_metadata(&path).map_err(|_| {
SvgRasterError::new(
"rasterizer-missing",
format!("provider component {role} is not present"),
)
})?;
if !metadata.file_type().is_file() {
return Err(SvgRasterError::new(
"rasterizer-missing",
format!("provider component {role} is not a literal regular file"),
));
}
let actual = crate::hash::hash_file(&path).map_err(|_| {
SvgRasterError::new(
"rasterizer-missing",
format!("provider component {role} cannot be read"),
)
})?;
if actual != entry.expected_blake3 {
return Err(SvgRasterError::new(
"rasterizer-hash-drift",
format!("provider component {role} does not match its pinned hash"),
));
}
let drift = |detail: String| SvgRasterError::new("rasterizer-hash-drift", detail);
if !super::provider::is_normal_form(&path) {
return Err(drift(format!(
"provider component {role} path is not in normal form"
)));
}
let real_path = super::provider::canonical(&path)
.ok_or_else(|| drift(format!("provider component {role} cannot be resolved")))?;
let mut real_roots = Vec::with_capacity(entry.closure_roots.len());
for root in &entry.closure_roots {
let root = Path::new(root);
if !super::provider::is_normal_form(root) {
return Err(drift(format!(
"a closure entry of provider component {role} is not in normal form"
)));
}
if super::provider::is_symlink_entry(root) {
return Err(drift(format!(
"a closure entry of provider component {role} is a symlink"
)));
}
if !super::provider::is_real_entry(root) {
return Err(drift(format!(
"a closure entry of provider component {role} is not a directory or a regular file"
)));
}
real_roots.push(super::provider::canonical(root).ok_or_else(|| {
drift(format!(
"a closure entry of provider component {role} cannot be resolved"
))
})?);
}
if !real_roots.is_empty()
&& !real_roots
.iter()
.any(|root| super::provider::lies_inside(&real_path, root))
{
return Err(drift(format!(
"provider component {role} lies outside its enumerated closure"
)));
}
if entry.closure_blake3.is_some() != !entry.closure_roots.is_empty() {
return Err(SvgRasterError::new(
"rasterizer-hash-drift",
format!(
"provider component {role} enumerates a closure without a pinned digest, or the reverse"
),
));
}
if let Some(expected) = &entry.closure_blake3 {
let roots: Vec<PathBuf> = entry.closure_roots.iter().map(PathBuf::from).collect();
let actual = closure_digest(&roots, &path)?;
if &actual != expected {
return Err(SvgRasterError::new(
"rasterizer-hash-drift",
format!(
"the runtime closure of provider component {role} does not match its pinned digest"
),
));
}
}
assert_version(&path, entry)?;
Ok(path)
}
fn assert_version(path: &Path, entry: &SvgRoleEntry) -> Result<(), SvgRasterError> {
let role = entry.role.as_str();
let drift = || {
SvgRasterError::new(
"rasterizer-version-drift",
format!("provider component {role} does not report its pinned version"),
)
};
let output = Command::new(path)
.arg("--version")
.env_clear()
.stdin(Stdio::null())
.output()
.map_err(|_| drift())?;
if !output.status.success() {
return Err(drift());
}
let text = String::from_utf8_lossy(&output.stdout);
let found = text
.split(|c: char| c.is_ascii_whitespace() || c == ':' || c == ',')
.any(|token| token == entry.version);
if found { Ok(()) } else { Err(drift()) }
}
fn discard_file(name: &str) -> std::io::Result<Stdio> {
Ok(Stdio::from(std::fs::File::create(name)?))
}
fn run_provider(
path: &Path,
argv: &[String],
role: &str,
profile_env: &[(String, String)],
) -> Result<(), SvgRasterError> {
let failed = || {
SvgRasterError::new(
"raster-exec-failed",
format!("provider component {role} did not complete cleanly"),
)
};
let mut command = Command::new(path);
command.args(argv).env_clear();
for (name, value) in profile_env {
command.env(name, value);
}
let status = command
.stdin(Stdio::null())
.stdout(discard_file(&format!("{role}.out")).map_err(|_| failed())?)
.stderr(discard_file(&format!("{role}.err")).map_err(|_| failed())?)
.status()
.map_err(|_| failed())?;
if !status.success() {
return Err(failed());
}
Ok(())
}
fn png_dimensions(path: &Path) -> Result<(u32, u32), SvgRasterError> {
let bytes = std::fs::read(path).map_err(|_| {
SvgRasterError::new(
"raster-exec-failed",
"the provider produced no readable raster",
)
})?;
let mut reader =
image::ImageReader::with_format(std::io::Cursor::new(bytes), image::ImageFormat::Png);
let mut limits = image::Limits::no_limits();
limits.max_alloc = Some(crate::convert::IMAGE_OCR_DECODE_ALLOC);
reader.limits(limits);
reader.into_dimensions().map_err(|_| {
SvgRasterError::new(
"raster-exec-failed",
"the provider produced a raster that does not decode",
)
})
}
fn profile_environment(
jail: &Path,
entry: &SvgRoleEntry,
) -> Result<Vec<(String, String)>, SvgRasterError> {
let mut env = Vec::new();
for (name, dir) in [
("HOME", "provider-home"),
("CFFIXED_USER_HOME", "provider-cfhome"),
("TMPDIR", "provider-tmp"),
] {
let path = jail.join(dir);
std::fs::create_dir_all(&path).map_err(|_| {
SvgRasterError::new(
"raster-exec-failed",
"the provider profile root cannot be created",
)
})?;
env.push((name.to_string(), path.display().to_string()));
}
if let Some(name) = &entry.jail_temp_env {
let path = jail.join("provider-tmp");
env.push((name.clone(), path.display().to_string()));
}
Ok(env)
}
pub(crate) trait ProviderRuntime {
fn verify(&self, entry: &SvgRoleEntry) -> Result<PathBuf, SvgRasterError>;
fn run(
&self,
path: &Path,
argv: &[String],
role: &str,
env: &[(String, String)],
) -> Result<(), SvgRasterError>;
fn dimensions(&self, path: &Path) -> Result<(u32, u32), SvgRasterError>;
fn read(&self, path: &Path) -> Result<Vec<u8>, SvgRasterError>;
}
pub(crate) struct JailedRuntime;
impl ProviderRuntime for JailedRuntime {
fn verify(&self, entry: &SvgRoleEntry) -> Result<PathBuf, SvgRasterError> {
verify_role(entry)
}
fn run(
&self,
path: &Path,
argv: &[String],
role: &str,
env: &[(String, String)],
) -> Result<(), SvgRasterError> {
run_provider(path, argv, role, env)
}
fn dimensions(&self, path: &Path) -> Result<(u32, u32), SvgRasterError> {
png_dimensions(path)
}
fn read(&self, path: &Path) -> Result<Vec<u8>, SvgRasterError> {
std::fs::read(path).map_err(|_| {
SvgRasterError::new(
"raster-exec-failed",
"the flattened raster cannot be read back",
)
})
}
}
pub(crate) fn rasterize(request: &SvgRasterRequest) -> Result<SvgRasterOk, SvgRasterError> {
rasterize_with(request, &JailedRuntime)
}
pub(crate) fn rasterize_with(
request: &SvgRasterRequest,
runtime: &dyn ProviderRuntime,
) -> Result<SvgRasterOk, SvgRasterError> {
let jail = std::env::current_dir().map_err(|_| {
SvgRasterError::new("raster-exec-failed", "the provider jail is unreachable")
})?;
let source = std::fs::read(Path::new(&request.input)).map_err(|_| {
SvgRasterError::new("raster-exec-failed", "the staged source cannot be read")
})?;
let geometry = parse_geometry(&source)?;
let (width, height) = geometry.window()?;
let raster = request
.roles
.iter()
.find(|entry| entry.role == PROVIDER_ROLE_RASTER)
.ok_or_else(|| {
SvgRasterError::new(
"rasterizer-missing",
format!("provider component {PROVIDER_ROLE_RASTER} is not present"),
)
})?;
let encoder = request
.roles
.iter()
.find(|entry| entry.role == PROVIDER_ROLE_ENCODER)
.ok_or_else(|| {
SvgRasterError::new(
"rasterizer-missing",
format!("provider component {PROVIDER_ROLE_ENCODER} is not present"),
)
})?;
let raster_path = runtime.verify(raster)?;
let raw = jail.join(JAIL_RAW);
let input = jail.join(&request.input);
let profile_env = profile_environment(&jail, raster)?;
runtime.run(
&raster_path,
&raster_argv(width, height, &raw, &input),
PROVIDER_ROLE_RASTER,
&profile_env,
)?;
let (raw_width, raw_height) = runtime.dimensions(&raw)?;
assert_geometry(geometry, raw_width, raw_height)?;
let area = u64::from(raw_width) * u64::from(raw_height);
if area > crate::convert::IMAGE_OCR_MAX_AREA_PX {
return Err(SvgRasterError::new(
"encoder-area-cap",
format!(
"the rendered raster is {area} px over the {} px encoder-input area cap",
crate::convert::IMAGE_OCR_MAX_AREA_PX
),
));
}
let encoder_path = runtime.verify(encoder)?;
let flat = jail.join(JAIL_FLAT);
runtime.run(
&encoder_path,
&flatten_argv(&raw, &flat),
PROVIDER_ROLE_ENCODER,
&[],
)?;
let (flat_width, flat_height) = runtime.dimensions(&flat)?;
if (flat_width, flat_height) != (raw_width, raw_height) {
return Err(SvgRasterError::new(
"raster-geometry-mismatch",
"the flattened raster does not keep the rendered dimensions",
));
}
let bytes = runtime.read(&flat)?;
Ok(SvgRasterOk {
raster_png_hex: hex_encode(&bytes),
width: flat_width,
height: flat_height,
})
}
pub(crate) fn hex_encode(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push(char::from_digit(u32::from(byte >> 4), 16).expect("nibble"));
out.push(char::from_digit(u32::from(byte & 0x0f), 16).expect("nibble"));
}
out
}
pub(crate) fn hex_decode(text: &str) -> Option<Vec<u8>> {
if !text.len().is_multiple_of(2) {
return None;
}
let bytes = text.as_bytes();
let mut out = Vec::with_capacity(text.len() / 2);
for pair in bytes.chunks_exact(2) {
let high = char::from(pair[0]).to_digit(16)?;
let low = char::from(pair[1]).to_digit(16)?;
out.push((high * 16 + low) as u8);
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(role: &str) -> SvgRoleEntry {
SvgRoleEntry {
role: role.to_string(),
path: "/deploy/component".to_string(),
expected_blake3: "a".repeat(64),
version: "1.2.3".to_string(),
closure_roots: Vec::new(),
closure_blake3: None,
jail_temp_env: None,
}
}
#[test]
fn declared_root_geometry_wins_and_units_are_explicit() {
let svg = |root: &str| format!("<?xml version=\"1.0\"?>\n<svg {root}><text>x</text></svg>");
let parse = |root: &str| parse_geometry(svg(root).as_bytes());
assert_eq!(
parse("xmlns=\"x\" width=\"1600\" height=\"900\"").unwrap(),
Geometry {
width: 1600.0,
height: 900.0
}
);
assert_eq!(
parse("width=\"1600px\" height=\"900px\"").unwrap(),
Geometry {
width: 1600.0,
height: 900.0
}
);
let inches = parse("width=\"2in\" height=\"1in\"").unwrap();
assert_eq!((inches.width, inches.height), (192.0, 96.0));
let points = parse("width=\"72pt\" height=\"36pt\"").unwrap();
assert_eq!((points.width, points.height), (96.0, 48.0));
let bordered =
parse("stroke-width=\"4\" width=\"800\" line-height=\"2\" height=\"600\"").unwrap();
assert_eq!((bordered.width, bordered.height), (800.0, 600.0));
let quoted = parse("height='600' width='800'").unwrap();
assert_eq!((quoted.width, quoted.height), (800.0, 600.0));
}
#[test]
fn a_view_box_is_the_fallback_and_percent_only_fails_closed() {
let view_box = parse_geometry(b"<svg viewBox=\"0 0 1600 900\"></svg>").unwrap();
assert_eq!((view_box.width, view_box.height), (1600.0, 900.0));
let commas = parse_geometry(b"<svg viewBox=\"0,0,1600,900\"></svg>").unwrap();
assert_eq!((commas.width, commas.height), (1600.0, 900.0));
let percent =
parse_geometry(b"<svg width=\"100%\" height=\"100%\" viewBox=\"0 0 400 200\"></svg>")
.unwrap();
assert_eq!((percent.width, percent.height), (400.0, 200.0));
for source in [
b"<svg width=\"100%\" height=\"100%\"></svg>".as_slice(),
b"<svg width=\"10em\" height=\"4em\"></svg>".as_slice(),
b"<svg width=\"50vw\" height=\"20vh\"></svg>".as_slice(),
b"<svg></svg>".as_slice(),
b"<svg width=\"0\" height=\"0\"></svg>".as_slice(),
b"<svg width=\"-40\" height=\"20\"></svg>".as_slice(),
b"<svg viewBox=\"0 0 400\"></svg>".as_slice(),
b"<svg viewBox=\"0 0 nan 200\"></svg>".as_slice(),
b"not markup at all".as_slice(),
b"<svg width=\"10\" height=\"10\"".as_slice(),
] {
let error = parse_geometry(source).unwrap_err();
assert_eq!(error.code, "raster-geometry-mismatch", "{source:?}");
}
}
#[test]
fn markup_before_the_root_cannot_supply_a_false_geometry() {
let decoys: [&[u8]; 6] = [
b"<!-- <svg width=\"1600\" height=\"900\"> --><svg><text>x</text></svg>",
b"<![CDATA[<svg width=\"1600\" height=\"900\">]]><svg><text>x</text></svg>",
b"<!DOCTYPE svg [ <!ENTITY e \"<svg width='1600' height='900'>\"> ]><svg><text>x</text></svg>",
b"<?xml version=\"1.0\"?><?decoy <svg width=\"1600\" height=\"900\"> ?><svg><text>x</text></svg>",
b"<svg><![CDATA[<svg width=\"1600\" height=\"900\">]]></svg>",
b"<html><svg width=\"1600\" height=\"900\"></svg></html>",
];
for source in decoys {
let error = parse_geometry(source).unwrap_err();
assert_eq!(error.code, "raster-geometry-mismatch", "{source:?}");
}
for real in [
b"<!-- <svg width=\"1600\" height=\"900\"> --><svg width=\"800\" height=\"450\"><text>x</text></svg>".as_slice(),
b"<![CDATA[<svg width=\"1600\" height=\"900\">]]><svg width=\"800\" height=\"450\"><text>x</text></svg>".as_slice(),
] {
let geometry = parse_geometry(real).unwrap();
assert_eq!((geometry.width, geometry.height), (800.0, 450.0));
}
}
#[test]
fn only_the_root_element_decides_the_geometry() {
let nested = parse_geometry(
b"<svg width=\"1600\" height=\"900\"><rect width=\"10\" height=\"10\"/></svg>",
)
.unwrap();
assert_eq!((nested.width, nested.height), (1600.0, 900.0));
let error = parse_geometry(b"<svg><rect width=\"10\" height=\"10\"/></svg>").unwrap_err();
assert_eq!(error.code, "raster-geometry-mismatch");
}
#[test]
fn both_cross_axis_checks_hold_within_one_pixel() {
let source = Geometry {
width: 1600.0,
height: 900.0,
};
assert!(assert_geometry(source, 1600, 900).is_ok());
assert!(assert_geometry(source, 1601, 901).is_ok());
assert!(assert_geometry(source, 1600, 901).is_err());
let error = assert_geometry(source, 1536, 1536).unwrap_err();
assert_eq!(error.code, "raster-geometry-mismatch");
assert!(assert_geometry(source, 1440, 900).is_err());
assert!(assert_geometry(source, 0, 900).is_err());
assert!(assert_geometry(source, 1600, 0).is_err());
let portrait = Geometry {
width: 816.0,
height: 1056.0,
};
assert!(assert_geometry(portrait, 1186, 1536).is_ok());
assert!(assert_geometry(portrait, 1536, 1536).is_err());
}
#[test]
fn the_window_is_the_declared_size_and_refuses_the_absurd() {
let (width, height) = Geometry {
width: 1600.4,
height: 899.6,
}
.window()
.unwrap();
assert_eq!((width, height), (1600, 900));
assert!(
Geometry {
width: 1e9,
height: 10.0
}
.window()
.is_err()
);
assert!(
Geometry {
width: 0.2,
height: 10.0
}
.window()
.is_err()
);
}
#[test]
fn the_rasterizer_argv_is_a_fixed_tuple_ending_in_the_one_flag() {
let argv = raster_argv(
1600,
900,
Path::new("/jail/raw.png"),
Path::new("/jail/doc.svg"),
);
assert_eq!(
argv,
vec![
"--headless=new",
"--disable-gpu",
"--hide-scrollbars",
"--window-size=1600,900",
"--force-device-scale-factor=1",
"--disable-background-networking",
"--disable-component-update",
"--disable-domain-reliability",
"--disable-sync",
"--no-first-run",
"--no-default-browser-check",
"--metrics-recording-only",
"--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE localhost",
"--screenshot=/jail/raw.png",
"file:///jail/doc.svg",
"--no-sandbox",
]
);
assert_eq!(argv.last().map(String::as_str), Some("--no-sandbox"));
assert_eq!(argv.iter().filter(|arg| *arg == "--no-sandbox").count(), 1);
assert!(!argv.iter().any(|arg| arg.contains("--user-data-dir")));
let other = raster_argv(
800,
600,
Path::new("/other/raw.png"),
Path::new("/other/d.svg"),
);
let differing: Vec<usize> = argv
.iter()
.zip(other.iter())
.enumerate()
.filter(|(_, (a, b))| a != b)
.map(|(index, _)| index)
.collect();
assert_eq!(differing, vec![3, 13, 14]);
}
#[test]
fn the_flatten_argv_carries_the_settled_options_in_order() {
let argv = flatten_argv(Path::new("/jail/raw.png"), Path::new("/jail/flat.png"));
assert_eq!(
argv,
vec![
"/jail/raw.png",
"-background",
"white",
"-alpha",
"remove",
"-alpha",
"off",
"-define",
"png:exclude-chunks=date,time",
"/jail/flat.png",
]
);
assert!(argv.iter().any(|arg| arg == "png:exclude-chunks=date,time"));
}
#[test]
fn a_missing_or_unreadable_component_is_named_missing_not_drifted() {
let mut role = entry(PROVIDER_ROLE_RASTER);
role.path = "/nonexistent/provider/component".to_string();
let error = verify_role(&role).unwrap_err();
assert_eq!(error.code, "rasterizer-missing");
assert!(error.message.contains(PROVIDER_ROLE_RASTER), "{error:?}");
assert!(!error.message.contains("nonexistent"), "{error:?}");
let dir = tempfile::tempdir().unwrap();
role.path = dir.path().to_str().unwrap().to_string();
let error = verify_role(&role).unwrap_err();
assert_eq!(error.code, "rasterizer-missing");
}
#[test]
fn a_hash_that_does_not_match_the_pin_is_drift() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("component");
std::fs::write(&path, b"substituted bytes").unwrap();
let mut role = entry(PROVIDER_ROLE_RASTER);
role.path = path.to_str().unwrap().to_string();
let error = verify_role(&role).unwrap_err();
assert_eq!(error.code, "rasterizer-hash-drift");
assert!(error.message.contains(PROVIDER_ROLE_RASTER), "{error:?}");
assert!(!error.message.contains("substituted"), "{error:?}");
}
#[test]
fn the_closure_digest_is_stable_ordered_and_sees_a_helper_change() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
let launcher = root.join("launcher");
std::fs::write(&launcher, b"launcher").unwrap();
std::fs::create_dir_all(root.join("helpers/inner")).unwrap();
let helper = root.join("helpers/helper");
let inner = root.join("helpers/inner/deep");
let data = root.join("helpers/resource.dat");
for (path, bytes) in [
(&helper, b"helper".as_slice()),
(&inner, b"deep".as_slice()),
(&data, b"data".as_slice()),
] {
std::fs::write(path, bytes).unwrap();
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
for path in [&launcher, &helper, &inner] {
let mut permissions = std::fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(path, permissions).unwrap();
}
}
let roots = vec![root.to_path_buf()];
let first = closure_digest(&roots, &launcher).unwrap();
assert_eq!(first, closure_digest(&roots, &launcher).unwrap());
std::fs::write(&launcher, b"a different launcher").unwrap();
assert_eq!(first, closure_digest(&roots, &launcher).unwrap());
std::fs::write(&helper, b"helper, but newer").unwrap();
assert_ne!(first, closure_digest(&roots, &launcher).unwrap());
}
#[test]
fn an_enumerated_closure_digests_every_entry_and_ignores_their_order() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
let launcher = root.join("launcher");
std::fs::write(&launcher, b"launcher").unwrap();
std::fs::create_dir_all(root.join("libs")).unwrap();
let inside = root.join("libs/one.dylib");
let standalone = root.join("two.dylib");
let unrelated = root.join("three.dylib");
for path in [&inside, &standalone, &unrelated] {
std::fs::write(path, b"library").unwrap();
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
for path in [&launcher, &inside, &standalone, &unrelated] {
let mut permissions = std::fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(path, permissions).unwrap();
}
}
let enumerated = vec![root.join("libs"), standalone.clone()];
let digest = closure_digest(&enumerated, &launcher).unwrap();
let reversed = vec![standalone.clone(), root.join("libs")];
assert_eq!(digest, closure_digest(&reversed, &launcher).unwrap());
std::fs::write(&standalone, b"library, but newer").unwrap();
assert_ne!(digest, closure_digest(&enumerated, &launcher).unwrap());
std::fs::write(&standalone, b"library").unwrap();
assert_eq!(digest, closure_digest(&enumerated, &launcher).unwrap());
std::fs::write(&inside, b"library, but newer").unwrap();
assert_ne!(digest, closure_digest(&enumerated, &launcher).unwrap());
std::fs::write(&inside, b"library").unwrap();
std::fs::write(&unrelated, b"nobody named me").unwrap();
assert_eq!(digest, closure_digest(&enumerated, &launcher).unwrap());
let widened = vec![root.join("libs"), standalone, unrelated];
assert_ne!(digest, closure_digest(&widened, &launcher).unwrap());
}
#[test]
fn every_regular_file_and_symlink_target_is_in_the_digest() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
let launcher = root.join("launcher");
std::fs::write(&launcher, b"launcher").unwrap();
std::fs::create_dir_all(root.join("lib/versions/1")).unwrap();
let policy = root.join("lib/policy.xml");
std::fs::write(&policy, b"<policymap/>").unwrap();
let link = root.join("lib/versions/current");
std::os::unix::fs::symlink("1", &link).unwrap();
let roots = vec![root.join("lib")];
let first = closure_digest(&roots, &launcher).unwrap();
std::fs::write(&policy, b"<policymap><policy/></policymap>").unwrap();
let edited = closure_digest(&roots, &launcher).unwrap();
assert_ne!(first, edited);
std::fs::write(&policy, b"<policymap/>").unwrap();
assert_eq!(first, closure_digest(&roots, &launcher).unwrap());
std::fs::remove_file(&link).unwrap();
std::os::unix::fs::symlink("2", &link).unwrap();
assert_ne!(first, closure_digest(&roots, &launcher).unwrap());
std::fs::remove_file(&link).unwrap();
std::os::unix::fs::symlink("1", &link).unwrap();
assert_eq!(first, closure_digest(&roots, &launcher).unwrap());
let outside = root.join("outside");
std::fs::create_dir_all(&outside).unwrap();
std::fs::write(outside.join("big"), vec![0u8; 1024]).unwrap();
std::os::unix::fs::symlink(&outside, root.join("lib/elsewhere")).unwrap();
let with_link = closure_digest(&roots, &launcher).unwrap();
std::fs::write(outside.join("big"), vec![1u8; 1024]).unwrap();
assert_eq!(with_link, closure_digest(&roots, &launcher).unwrap());
}
#[test]
fn a_closure_entry_that_is_a_symlink_is_refused_before_the_walk() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path().canonicalize().unwrap();
let real = root.join("real");
std::fs::create_dir_all(&real).unwrap();
let component = real.join("component");
std::fs::write(&component, b"component bytes").unwrap();
let link = root.join("link");
std::os::unix::fs::symlink(&real, &link).unwrap();
let error = closure_digest(std::slice::from_ref(&link), &component).unwrap_err();
assert_eq!(error.code, "rasterizer-hash-drift");
assert!(error.message.contains("symlink"), "{error:?}");
let mut role = entry(PROVIDER_ROLE_RASTER);
role.path = component.to_str().unwrap().to_string();
role.expected_blake3 = crate::hash::hash_file(&component).unwrap();
role.closure_roots = vec![link.to_str().unwrap().to_string()];
role.closure_blake3 = Some("b".repeat(64));
let error = verify_role(&role).unwrap_err();
assert_eq!(error.code, "rasterizer-hash-drift");
assert!(error.message.contains("symlink"), "{error:?}");
role.closure_roots = vec![real.to_str().unwrap().to_string()];
let error = verify_role(&role).unwrap_err();
assert!(error.message.contains("pinned digest"), "{error:?}");
let socket = root.join("sock");
let _listener = std::os::unix::net::UnixListener::bind(&socket).unwrap();
role.closure_roots = vec![socket.to_str().unwrap().to_string()];
let error = verify_role(&role).unwrap_err();
assert_eq!(error.code, "rasterizer-hash-drift");
assert!(
error.message.contains("not a directory or a regular file"),
"{error:?}"
);
let fifo = root.join("fifo");
assert!(
std::process::Command::new("/usr/bin/mkfifo")
.arg(&fifo)
.status()
.unwrap()
.success()
);
role.closure_roots = vec![fifo.to_str().unwrap().to_string()];
let error = verify_role(&role).unwrap_err();
assert!(
error.message.contains("not a directory or a regular file"),
"{error:?}"
);
}
#[test]
fn the_worker_decides_containment_on_resolved_paths() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path().canonicalize().unwrap();
let closure = root.join("closure");
let outside = root.join("outside");
std::fs::create_dir_all(&closure).unwrap();
std::fs::create_dir_all(&outside).unwrap();
let escaped = outside.join("component");
std::fs::write(&escaped, b"component bytes").unwrap();
std::os::unix::fs::symlink(&outside, closure.join("link")).unwrap();
let mut role = entry(PROVIDER_ROLE_RASTER);
role.path = closure.join("link/component").to_str().unwrap().to_string();
role.expected_blake3 = crate::hash::hash_file(&escaped).unwrap();
role.closure_roots = vec![closure.to_str().unwrap().to_string()];
role.closure_blake3 = Some("b".repeat(64));
let error = verify_role(&role).unwrap_err();
assert_eq!(error.code, "rasterizer-hash-drift");
assert!(error.message.contains("outside"), "{error:?}");
let inside = closure.join("component");
std::fs::write(&inside, b"component bytes").unwrap();
std::os::unix::fs::symlink(&closure, root.join("alias")).unwrap();
role.path = root.join("alias/component").to_str().unwrap().to_string();
let error = verify_role(&role).unwrap_err();
assert!(error.message.contains("pinned digest"), "{error:?}");
role.path = closure
.join("../closure/component")
.to_str()
.unwrap()
.to_string();
let error = verify_role(&role).unwrap_err();
assert!(error.message.contains("normal form"), "{error:?}");
}
#[test]
fn a_closure_and_its_digest_are_a_pair_at_the_worker_too() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("component");
std::fs::write(&path, b"component bytes").unwrap();
let good = crate::hash::hash_file(&path).unwrap();
let mut roots_only = entry(PROVIDER_ROLE_RASTER);
roots_only.path = path.to_str().unwrap().to_string();
roots_only.expected_blake3 = good.clone();
roots_only.closure_roots = vec![dir.path().to_str().unwrap().to_string()];
let error = verify_role(&roots_only).unwrap_err();
assert_eq!(error.code, "rasterizer-hash-drift");
assert!(
error.message.contains("without a pinned digest"),
"{error:?}"
);
let mut digest_only = entry(PROVIDER_ROLE_RASTER);
digest_only.path = path.to_str().unwrap().to_string();
digest_only.expected_blake3 = good;
digest_only.closure_blake3 = Some("0".repeat(64));
let error = verify_role(&digest_only).unwrap_err();
assert_eq!(error.code, "rasterizer-hash-drift");
}
#[test]
fn hex_round_trips_and_refuses_malformed_text() {
let bytes: Vec<u8> = (0..=255u8).collect();
let text = hex_encode(&bytes);
assert_eq!(text.len(), 512);
assert_eq!(hex_decode(&text).unwrap(), bytes);
assert_eq!(hex_encode(&[]), "");
assert_eq!(hex_decode("").unwrap(), Vec::<u8>::new());
assert!(hex_decode("abc").is_none());
assert!(hex_decode("zz").is_none());
}
}