mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 23:01:02 +00:00
Scripts: a framed run keeps the file-access roots it started under
A script.run that a control request started in the app's frames was advanced by later frames, which run unconfined, so its vc.run file commands could read and write outside the caller's automation roots. The job now keeps the file-access scope of its script.run and runs each slice in it, as background opening and the folder walker do.
This commit is contained in:
@@ -502,6 +502,8 @@ struct Job {
|
||||
/// The documents open when the run began, by [`DocState::uid`].
|
||||
start_uids: Vec<u64>,
|
||||
name: String,
|
||||
/// The file-access scope `script.run` came in, which every slice runs in.
|
||||
scope: crate::file_access::ScopeHandle,
|
||||
}
|
||||
|
||||
/// The `Data` for a JSON value, within `limits`. When it does not fit, the error is the place in
|
||||
@@ -986,7 +988,8 @@ impl Session {
|
||||
self.script_report = Some(report);
|
||||
return Err(EngineError::Other(message));
|
||||
}
|
||||
let job = Job { realm, host, world: World::working(self), start_uids: open, name: request.name };
|
||||
let scope = crate::file_access::ScopeHandle::capture();
|
||||
let job = Job { realm, host, world: World::working(self), start_uids: open, name: request.name, scope };
|
||||
let id = NEXT_JOB.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
JOBS.with(|jobs| jobs.borrow_mut().insert(id, job));
|
||||
self.script_key = Some(JobKey(id));
|
||||
@@ -1170,20 +1173,24 @@ fn failure_text(report: &Value) -> String {
|
||||
}
|
||||
|
||||
/// A run's slice: the working copy in, the real session lent to the host functions, one measured
|
||||
/// slice run, and everything back. A panic in the interpreter is the outer `Err`.
|
||||
/// slice run in the file-access scope the run began in, and everything back. A panic in the
|
||||
/// interpreter is the outer `Err`.
|
||||
fn run_slice(s: &mut Session, job: &mut Job) -> std::result::Result<std::result::Result<Progress, RunError>, String> {
|
||||
let depth = std::mem::replace(&mut s.depth, 0);
|
||||
job.world.swap(s);
|
||||
s.script_in_run = true;
|
||||
let cell = job.host.session.clone();
|
||||
std::mem::swap(s, &mut *cell.borrow_mut());
|
||||
let scope = job.scope.clone();
|
||||
let realm = &mut job.realm;
|
||||
let r = guard::catch_panic(|| {
|
||||
#[cfg(test)]
|
||||
if PANIC_IN_SLICE.with(Cell::get) {
|
||||
panic!("a slice failed");
|
||||
}
|
||||
realm.advance(SLICE_FUEL)
|
||||
let r = scope.run(|| {
|
||||
guard::catch_panic(|| {
|
||||
#[cfg(test)]
|
||||
if PANIC_IN_SLICE.with(Cell::get) {
|
||||
panic!("a slice failed");
|
||||
}
|
||||
realm.advance(SLICE_FUEL)
|
||||
})
|
||||
});
|
||||
std::mem::swap(s, &mut *cell.borrow_mut());
|
||||
s.script_in_run = false;
|
||||
|
||||
@@ -862,6 +862,44 @@ fn a_file_saved_before_a_failure_stays_written_and_is_named() {
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
/// A run in the app's frames keeps the file-access scope its `script.run` came in, whatever scope
|
||||
/// the frames that advance it run in.
|
||||
#[test]
|
||||
fn a_framed_run_keeps_the_file_access_scope_it_started_in() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-script-scope-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let (allowed, outside) = (dir.join("allowed"), dir.join("outside"));
|
||||
std::fs::create_dir_all(&allowed).unwrap();
|
||||
std::fs::create_dir_all(&outside).unwrap();
|
||||
let roots = crate::file_access::AutomationRoots::new(&[&allowed], &[&allowed]).unwrap().unwrap();
|
||||
let export = |target: &std::path::Path| {
|
||||
let p = target.to_string_lossy().into_owned();
|
||||
format!("try {{ vc.run('document.export', {{path: {p:?}, format: 'svg'}}); vc.log('ok'); }} catch (e) {{ vc.log(e.name); }}")
|
||||
};
|
||||
let finish = |s: &mut Session| {
|
||||
loop {
|
||||
if let Some(r) = s.script_pump() {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut s = two_docs();
|
||||
s.script_frames = true;
|
||||
let confined_target = outside.join("confined.svg");
|
||||
let started = crate::file_access::confine(Some(&roots), || run(&mut s, &export(&confined_target))).unwrap();
|
||||
assert_eq!(started["running"], true);
|
||||
let r = crate::file_access::unconfined(|| finish(&mut s)).unwrap();
|
||||
assert_eq!(r["log"], json!(["Error"]), "a confined caller's run stays confined: {r}");
|
||||
assert!(!confined_target.exists());
|
||||
let user_target = outside.join("user.svg");
|
||||
let started = crate::file_access::unconfined(|| run(&mut s, &export(&user_target))).unwrap();
|
||||
assert_eq!(started["running"], true);
|
||||
let r = crate::file_access::confine(Some(&roots), || finish(&mut s)).unwrap();
|
||||
assert_eq!(r["log"], json!(["ok"]), "a run the person started is not confined by a later caller: {r}");
|
||||
assert!(user_target.exists());
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_committed_run_exports_what_it_drew() {
|
||||
let path = std::env::temp_dir().join(format!("vc-script-export-{}.svg", std::process::id()));
|
||||
|
||||
Reference in New Issue
Block a user