mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 18:54:55 +00:00
Stores: saving a preset, profile or plug-in reads at most one byte past it from the file it replaces
Before writing a PDF preset, ICC profile, plug-in, Color Settings, disabled list or grants file, the store compared the new bytes with the file already there by reading the whole file into memory, so a large file left in the folder was read in full. The comparison now reads at most one byte more than the new content.
This commit is contained in:
@@ -135,11 +135,18 @@ fn too_large(max: u64) -> String {
|
||||
format!("larger than {max} bytes")
|
||||
}
|
||||
|
||||
/// Whether `old` holds exactly `bytes`. It is read no further than one byte past their length, so a
|
||||
/// large file is not read into memory.
|
||||
pub(crate) fn holds(old: impl Read, bytes: &[u8]) -> bool {
|
||||
let mut read = vec![];
|
||||
old.take(bytes.len() as u64 + 1).read_to_end(&mut read).is_ok() && read == bytes
|
||||
}
|
||||
|
||||
/// Write `bytes` to `dir/file`, making the folder; nothing is written when the file has them
|
||||
/// already. A failure is logged: remembering is best effort and never fails the command.
|
||||
fn put(dir: &Path, file: &str, bytes: &[u8]) -> bool {
|
||||
let path = dir.join(file);
|
||||
if std::fs::read(&path).is_ok_and(|old| old == bytes) {
|
||||
if std::fs::File::open(&path).is_ok_and(|old| holds(old, bytes)) {
|
||||
return true;
|
||||
}
|
||||
let written = std::fs::create_dir_all(dir).and_then(|()| write_atomic(&path, bytes));
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde_json::{Value, json};
|
||||
use vectorcraft_color::cms;
|
||||
|
||||
use crate::Session;
|
||||
use crate::store::{MAX_PRESET_FILE, file_stem};
|
||||
use crate::store::{MAX_PRESET_FILE, file_stem, holds};
|
||||
|
||||
fn fresh_dir(tag: &str) -> std::path::PathBuf {
|
||||
let dir = vectorcraft_testkit::temp_dir(&format!("store-{tag}"));
|
||||
@@ -128,6 +128,47 @@ fn a_folder_that_does_not_exist_yet_takes_the_presets_the_preferences_held() {
|
||||
assert_eq!(saved_names(&mut b), ["From Preferences"]);
|
||||
}
|
||||
|
||||
/// Serves `0` bytes without end and counts how many were read.
|
||||
struct Endless(u64);
|
||||
|
||||
impl std::io::Read for Endless {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
if self.0 > 1 << 24 {
|
||||
return Err(std::io::Error::other("read too far"));
|
||||
}
|
||||
buf.fill(0);
|
||||
self.0 += buf.len() as u64;
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_write_reads_the_file_it_replaces_only_one_byte_past_the_new_length() {
|
||||
let mut old = Endless(0);
|
||||
assert!(!holds(&mut old, &[0; 10]));
|
||||
assert_eq!(old.0, 11);
|
||||
assert!(holds(&[1, 2, 3][..], &[1, 2, 3]));
|
||||
assert!(!holds(&[1, 2, 3, 4][..], &[1, 2, 3]));
|
||||
assert!(!holds(&[1, 2][..], &[1, 2, 3]));
|
||||
assert!(holds(&[][..], &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_saved_preset_replaces_an_oversized_file_in_its_place() {
|
||||
let dir = fresh_dir("oversized-put").join("PDF Presets");
|
||||
let mut a = Session::new();
|
||||
a.attach_pdf_preset_store(dir.clone());
|
||||
a.execute("pdf.preset.save", &json!({"name": "Web"})).unwrap();
|
||||
let file = dir.join(format!("{}.vcpdfpresets", file_stem("Web")));
|
||||
let saved = std::fs::read(&file).unwrap();
|
||||
let mut oversized = saved.clone();
|
||||
oversized.resize(usize::try_from(MAX_PRESET_FILE).unwrap() * 2, b' ');
|
||||
std::fs::write(&file, &oversized).unwrap();
|
||||
|
||||
a.execute("pdf.preset.save", &json!({"name": "Web"})).unwrap();
|
||||
assert_eq!(std::fs::read(&file).unwrap(), saved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damaged_unsafe_and_oversized_preset_files_are_skipped() {
|
||||
let dir = fresh_dir("damaged").join("PDF Presets");
|
||||
|
||||
Reference in New Issue
Block a user