use super::*; fn book(rtl: bool, cells: &[(&str, gridcraft_core::Value, gridcraft_model::HAlign)]) -> Workbook { let mut wb = Workbook::new(); let ids: Vec<_> = cells.iter().map(|(_, _, h)| wb.styles.derive(gridcraft_model::StyleId::DEFAULT, |st| st.align.h = *h)).collect(); let sh = wb.sheet_mut(0).unwrap(); sh.right_to_left = rtl; for ((at, value, _), id) in cells.iter().zip(ids) { let c = CellRef::parse(at).unwrap(); sh.set_value(c, value.clone()); sh.set_style(c, id); } wb } fn copied(wb: &Workbook, range: &str) -> String { let r = RangeRef::parse(range).unwrap(); range_html(wb, 0, r, &crate::display::range_text(wb, 0, r)).unwrap() } #[cfg(not(target_arch = "wasm32"))] #[cfg(unix)] #[test] fn save_fails_upfront_when_the_temp_name_is_taken() { // Another local user who can write the directory must not be able to point the // save's predictable temporary name at their own file: creation is exclusive. let dir = std::env::temp_dir().join(format!("gridcraft-fs-safety-temp-name-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let victim = dir.join("victim.txt"); std::fs::write(&victim, b"original").unwrap(); let dest = dir.join("book.xlsx"); std::os::unix::fs::symlink(&victim, dir.join("book.xlsx.sctmp")).unwrap(); let err = write_file(dest.to_str().unwrap(), b"new").unwrap_err(); assert_eq!(std::fs::read(&victim).unwrap(), b"original"); assert!(!dir.join("book.xlsx").exists(), "{err}"); let _ = std::fs::remove_dir_all(&dir); } #[cfg(not(target_arch = "wasm32"))] #[cfg(unix)] #[test] fn save_keeps_the_destination_access_restrictions() { use std::os::unix::fs::PermissionsExt; let dir = std::env::temp_dir().join(format!("gridcraft-fs-safety-permissions-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let dest = dir.join("private.xlsx"); std::fs::write(&dest, b"v1").unwrap(); std::fs::set_permissions(&dest, std::fs::Permissions::from_mode(0o600)).unwrap(); write_file(dest.to_str().unwrap(), b"v2").unwrap(); let mode = std::fs::metadata(&dest).unwrap().permissions().mode() & 0o777; assert_eq!(mode, 0o600); // And a brand-new file is created with the restrictive, not permissive, default. let fresh = dir.join("new.xlsx"); write_file(fresh.to_str().unwrap(), b"v").unwrap(); let mode = std::fs::metadata(&fresh).unwrap().permissions().mode() & 0o777; assert!(mode & 0o022 == 0, "mode {mode:o}"); let _ = std::fs::remove_dir_all(&dir); } #[cfg(not(target_arch = "wasm32"))] #[test] fn open_refuses_directories_and_unknowable_lengths() { let dir = std::env::temp_dir().join(format!("gridcraft-fs-safety-open-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); assert!(read_file(dir.to_str().unwrap()).is_err(), "a directory is not a regular file"); assert!(read_file(dir.join("missing").to_str().unwrap()).is_err()); let _ = std::fs::remove_dir_all(&dir); } #[test] fn html_export_refuses_an_outsized_grid_instead_of_growing_forever() { // #2387: the used rectangle alone bounds the output (every row writes the whole // column span), so a sparse sheet with one cell at A1 and another far right // 300 rows down is 4.9M cells → refused before a byte of `