mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-31 22:07:06 -06:00
#3692: confined automation adds or removes fonts only when the font store is inside its write root
Finding 44: font.add stored the file in the user font folder and font.remove deleted stored files directly, bypassing the automation write root (#769). Both now check the user font folder against the write policy in force before touching the database or the disk; with no folder (headless) a font still lasts for the session only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b246ea166a
commit
bcce02ab36
@@ -43,6 +43,15 @@ fn read_font_file(path: &str) -> Result<Vec<u8>, CmdError> {
|
||||
crate::io::read_file_capped(path, wordcraft_fonts::MAX_FONT_FILE_BYTES as u64, "font file").map_err(CmdError::Failed)
|
||||
}
|
||||
|
||||
/// Adding or removing a font writes to the user font folder: automation confined to a write root
|
||||
/// (#769) may do that only when the folder is inside it.
|
||||
fn check_font_store(dir: Option<&std::path::Path>) -> Result<(), CmdError> {
|
||||
match dir {
|
||||
Some(dir) => crate::file_access::check_write(&dir.to_string_lossy()).map_err(CmdError::Failed),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn add(s: &mut Session, v: &Value) -> CmdResult {
|
||||
if p::str(v, "url").is_some() {
|
||||
return Err(CmdError::Failed(
|
||||
@@ -71,6 +80,7 @@ fn add(s: &mut Session, v: &Value) -> CmdResult {
|
||||
s.push_ui_request(json!({"open": "addFont"}));
|
||||
return Ok(json!({"pending": true}));
|
||||
};
|
||||
check_font_store(FontDb::global().user_font_dir().as_deref())?;
|
||||
let r = FontDb::global().install_font(&name, &bytes).map_err(|e| CmdError::Failed(e.to_string()))?;
|
||||
// Text already set in the family (shown in a fallback until now) takes the new font.
|
||||
s.relayout();
|
||||
@@ -100,8 +110,33 @@ fn list(_: &mut Session, v: &Value) -> CmdResult {
|
||||
|
||||
fn remove(s: &mut Session, v: &Value) -> CmdResult {
|
||||
let family = p::str(v, "family").filter(|f| !f.trim().is_empty()).ok_or_else(|| CmdError::Params("family is required".into()))?;
|
||||
check_font_store(FontDb::global().user_font_dir().as_deref())?;
|
||||
let gone = FontDb::global().remove_user_font(family.trim()).map_err(|e| CmdError::Failed(e.to_string()))?;
|
||||
s.relayout();
|
||||
s.status = format!("Removed {}", family.trim());
|
||||
Ok(json!({"removed": gone.iter().map(font_json).collect::<Vec<_>>()}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn confined_automation_changes_only_a_font_store_inside_its_write_root() {
|
||||
let root = std::env::temp_dir().join(format!("wordcraft-font-store-{}", std::process::id()));
|
||||
let outside = std::env::temp_dir().join(format!("wordcraft-font-store-out-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
std::fs::create_dir_all(&outside).unwrap();
|
||||
let write = crate::file_access::AutomationRoots::new(None, Some(&root)).unwrap();
|
||||
let read_only = crate::file_access::AutomationRoots::new(Some(&root), None).unwrap();
|
||||
crate::file_access::confine(write.as_ref(), || {
|
||||
assert!(check_font_store(None).is_ok(), "no folder: the font lasts for the session");
|
||||
assert!(check_font_store(Some(&root.join("fonts"))).is_ok());
|
||||
assert!(check_font_store(Some(&outside)).is_err());
|
||||
});
|
||||
crate::file_access::confine(read_only.as_ref(), || assert!(check_font_store(Some(&root)).is_err()));
|
||||
assert!(check_font_store(Some(&outside)).is_ok(), "unconfined: the person at the keyboard");
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
let _ = std::fs::remove_dir_all(&outside);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user