Merge pull request #5394 from storytold/fix/sec-followup-5306

Place: a placement read again when it completes stays within the roots it was read in
This commit is contained in:
BFlat
2026-11-03 22:43:41 +07:00
committed by GitHub
2 changed files with 45 additions and 2 deletions
+6 -2
View File
@@ -137,6 +137,9 @@ pub struct Pending {
/// The number of the paste's chain ([`Placing::chains`]).
chain: Option<u64>,
done: Receiver<Result<Read, String>>,
/// The automation roots in force when the read started, kept in force when it is placed: a
/// DXF placed against an artboard that changed meanwhile is read again then.
scope: ScopeHandle,
}
/// A read waiting for the thread that reads the files.
@@ -318,6 +321,7 @@ fn spawn_labeled(
let (tx, done) = channel();
// The automation roots in force here are in force there.
let scope = ScopeHandle::capture();
let completion = scope.clone();
#[cfg(test)]
let reading = app.placing.reading.clone();
let job: Job = Box::new(move || {
@@ -352,7 +356,7 @@ fn spawn_labeled(
let _ = reader.send(job);
app.placing.reader = Some(reader);
}
app.placing.pending.push(Pending { name, label: label.clone(), doc, dropped, paste: chain.is_some(), chain, done });
app.placing.pending.push(Pending { name, label: label.clone(), doc, dropped, paste: chain.is_some(), chain, done, scope: completion });
let status = format!("{label}…");
app.status(status.clone());
Ok(Some(json!({ "background": true, "status": status })))
@@ -384,7 +388,7 @@ pub fn poll(app: &mut VectorcraftApp) {
if app.ui.status == format!("{}…", p.label) {
app.ui.status.clear();
}
finish(app, &p, r);
p.scope.clone().run(|| finish(app, &p, r));
if let Some(chain) = p.chain
&& !app.placing.swatch_paste.as_ref().is_some_and(|waiting| waiting.chain == chain)
{
+39
View File
@@ -1284,3 +1284,42 @@ fn a_file_read_between_a_tools_clicks_is_placed() {
assert_eq!(app.session.tool_busy(), !other_document, "other document: {other_document}");
}
}
/// A DXF placed within automation roots is read again within them when it is placed against an
/// artboard that changed meanwhile: a path redirected outside the roots after the read is refused.
#[cfg(unix)]
#[test]
fn a_file_read_again_when_placed_stays_within_the_roots_it_was_read_in() {
let mut src = Session::new();
src.execute("file.new", &json!({"width": 100, "height": 50})).unwrap();
src.execute("shape.rectangle", &json!({"x": 5, "y": 5, "width": 20, "height": 10})).unwrap();
let v = src.execute("document.serialize", &json!({"format": "dxf"})).unwrap();
let dxf = vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap();
let base = std::env::temp_dir().join(format!("vc-placing-reread-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
for d in ["in", "out"] {
std::fs::create_dir_all(base.join(d)).unwrap();
std::fs::write(base.join(d).join("real.dxf"), &dxf).unwrap();
}
let link = base.join("in").join("plan.dxf");
std::os::unix::fs::symlink(base.join("in").join("real.dxf"), &link).unwrap();
let roots = vectorcraft_engine::file_access::AutomationRoots::new(&[&base.join("in")], &[]).unwrap().unwrap();
let mut app = app(true);
let p = json!({"path": link.to_string_lossy(), "dxf": {"fit": true}, "at": [100, 150]});
vectorcraft_engine::file_access::confine(Some(&roots), || start(&mut app, &p, false)).unwrap();
let t0 = std::time::Instant::now();
while !(app.placing.reading[1].load(std::sync::atomic::Ordering::SeqCst) == 1
&& app.placing.reading[0].load(std::sync::atomic::Ordering::SeqCst) == 0)
{
assert!(t0.elapsed().as_secs() < 60, "read within 60 s");
std::thread::yield_now();
}
// Read; now the path leads outside the roots and the artboard under `at` changes.
std::fs::remove_file(&link).unwrap();
std::os::unix::fs::symlink(base.join("out").join("real.dxf"), &link).unwrap();
app.run("artboard.setProps", json!({"index": 0, "width": 200})).unwrap();
placed(&mut app);
assert_eq!(app.session.doc().unwrap().doc.layers.iter().flat_map(|l| l.children().into_iter().flatten()).count(), 0, "nothing placed");
assert!(app.ui.status.contains("outside the read root"), "{}", app.ui.status);
let _ = std::fs::remove_dir_all(base);
}