craft-fonts-build: reject faces under a non-UTF-8 path instead of embedding a lossy include_bytes! path

This commit is contained in:
aiongg committed 2026-10-23 05:05:13 +00:00
1 parent d413913b6b
commit a30ff6bf2f
2 files changed
+26 -5

No files matched your search

+12 -5
View File
@@ -181,8 +181,9 @@ impl Embed {
self.check_coverage(faces, dir, cargo);
}
}
let (table, faces) = match selected {
Ok(faces) => (table(&faces), faces.into_iter().map(|face| face.entry).collect()),
let tabled = selected.and_then(|faces| Ok((table(&faces)?, faces.into_iter().map(|face| face.entry).collect())));
let (table, faces) = match tabled {
Ok(tabled) => tabled,
Err(e) if required => {
write(&out, EMPTY_TABLE)?;
return Err(e);
@@ -400,9 +401,12 @@ pub(crate) struct Env {
pub out_dir: Option<OsString>,
}
fn table(faces: &[Face]) -> String {
/// The generated source; `include_bytes!` takes a string, so a face under a path that isn't UTF-8
/// is an error rather than a lossy path the consumer's build can't find.
fn table(faces: &[Face]) -> Result<String, Error> {
let mut src = String::from("// Generated by craft-fonts-build. Do not edit.\n&[\n");
for Face { entry, path, index, .. } in faces {
let file = path.to_str().ok_or_else(|| Error::NonUtf8Path { path: path.clone() })?;
let scripts: Vec<String> = entry.scripts.iter().map(|s| format!("{s:?}")).collect();
let _ = writeln!(
src,
@@ -411,11 +415,11 @@ fn table(faces: &[Face]) -> String {
entry.style,
scripts.join(", "),
index,
path.display().to_string(),
file,
);
}
src.push_str("]\n");
src
Ok(src)
}
fn write(path: &Path, contents: &str) -> Result<(), Error> {
@@ -446,6 +450,8 @@ pub enum Error {
Subset { path: PathBuf, reason: String },
/// [`Embed::documents`] names families the manifest lacks; `known` lists the manifest's.
UnknownFamilies { manifest: PathBuf, families: Vec<String>, known: Vec<String> },
/// A face to embed has a path that isn't UTF-8, which `include_bytes!` can't name.
NonUtf8Path { path: PathBuf },
}
impl fmt::Display for Error {
@@ -477,6 +483,7 @@ impl fmt::Display for Error {
families.join(", "),
known.join(", ")
),
Self::NonUtf8Path { path } => write!(f, "cannot embed {}: include_bytes! needs a UTF-8 path", path.display()),
}
}
}
+14
View File
@@ -115,6 +115,20 @@ fn required_fails_without_a_usable_checkout() {
assert!(matches!(run(Embed::new(), Some(&scratch.0), Some("1")).result, Err(Error::NoFaces { .. })));
}
#[cfg(unix)]
#[test]
fn rejects_a_checkout_under_a_non_utf8_path() {
use std::os::unix::ffi::OsStrExt;
let scratch = Scratch::new("non-utf8");
let dir = scratch.0.join(std::ffi::OsStr::from_bytes(b"craft-fonts-\xff"));
std::fs::create_dir_all(dir.join("fonts")).unwrap();
std::fs::copy(fixture().join("fonts/sans-arabic/SansArabic.ttf"), dir.join("fonts/a.ttf")).unwrap();
std::fs::write(dir.join("fonts/manifest.txt"), "A | Regular | fonts/a.ttf | Arab | OFL-1.1 | OFL.txt | 00 | src\n").unwrap();
let r = run(Embed::new(), Some(&dir), Some("1"));
assert!(matches!(r.result, Err(Error::NonUtf8Path { .. })), "{:?}", r.result);
assert_eq!(r.table.trim(), "&[]");
}
#[test]
fn embeds_the_selected_scripts() {
let r = run(Embed::new(), Some(&fixture()), Some("1"));