Merge pull request #6529 from dotCooCoo/fix/store-put-size-bound

Stores: saving a preset, profile or plug-in reads at most one byte past it from the file it replaces
This commit is contained in:
BFlat
2026-11-11 14:17:50 +07:00
committed by GitHub
2 changed files with 50 additions and 2 deletions
+8 -1
View File
@@ -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));
+42 -1
View File
@@ -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");