mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 06:06:38 +00:00
Plug-ins: a file past an importer's limit is refused from its size, without reading it
document.open by path reads the first 64 KiB of a file only an importer reads to look for a built-in format. A larger file is read to the limit only when its size is within the limit, so an over-limit file costs no 128 MiB read and a built-in file over the limit is not held in memory twice.
This commit is contained in:
@@ -58,22 +58,36 @@ pub(super) fn pick(name: &str, bytes: &[u8], p: &Value) -> Result<Option<Arc<Plu
|
||||
/// The file at `path` for `document.open`'s params `p`. A file that only an importer reads (one
|
||||
/// `importer` names, or an extension no built-in format reads that an importer lists) is read up
|
||||
/// to [`import::MAX_INPUT_BYTES`]: past that, it is an error, unless the start of the file shows a
|
||||
/// built-in format and no importer was named, which is read whole as any other file is.
|
||||
/// built-in format and no importer was named, which is read whole as any other file is. A file past
|
||||
/// the limit that isn't read whole is reported from its size, without reading it.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn read_path(path: &str, p: &Value) -> Result<Vec<u8>> {
|
||||
/// How much of the file [`detect`] is given: more than its content checks read.
|
||||
const SNIFF_BYTES: usize = 64 << 10;
|
||||
|
||||
let ext = extension(path);
|
||||
let explicit = named(p)?.is_some();
|
||||
if !explicit && (super::readable_format(&ext).is_some() || import::for_extension(&ext).is_empty()) {
|
||||
return super::read_file(path);
|
||||
}
|
||||
let head = super::read_file_head(path, import::MAX_INPUT_BYTES + 1)?;
|
||||
if head.len() <= import::MAX_INPUT_BYTES {
|
||||
let head = super::read_file_head(path, SNIFF_BYTES)?;
|
||||
if head.len() < SNIFF_BYTES {
|
||||
return Ok(head);
|
||||
}
|
||||
if !explicit && detect(path, &head).is_some() {
|
||||
return super::read_file(path);
|
||||
}
|
||||
Err(err(format!("can't open `{}`: {}", file_name(path), import::too_large())))
|
||||
let too_large = || err(format!("can't open `{}`: {}", file_name(path), import::too_large()));
|
||||
let size = std::fs::metadata(path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?.len();
|
||||
if size > import::MAX_INPUT_BYTES as u64 {
|
||||
return Err(too_large());
|
||||
}
|
||||
// The file can grow after the size is read.
|
||||
let file = super::read_file_head(path, import::MAX_INPUT_BYTES + 1)?;
|
||||
if file.len() > import::MAX_INPUT_BYTES {
|
||||
return Err(too_large());
|
||||
}
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@@ -878,3 +878,26 @@ fn a_photo_places_the_way_its_exif_orientation_says() {
|
||||
let red = |p: &image::Rgb<u8>| p[0] > 150 && p[1] < 80;
|
||||
assert!(red(px.get_pixel(4, 3)) && !red(px.get_pixel(4, 12)), "the left half is on top");
|
||||
}
|
||||
|
||||
/// With `importer` named, a file up to the importer's limit is read whole and a larger one is an
|
||||
/// error, whatever its content.
|
||||
#[test]
|
||||
fn read_path_for_a_named_importer_stops_at_the_limit() {
|
||||
let limit = vectorcraft_plugins::import::MAX_INPUT_BYTES as u64;
|
||||
let dir = tmp_dir("read-path-limit");
|
||||
let path = dir.join("drawing.tuv");
|
||||
let name = path.to_string_lossy().into_owned();
|
||||
let named = json!({"importer": "plugin.test.notinstalled"});
|
||||
let sized = |len: u64| {
|
||||
std::fs::write(&path, b"\x89PNG\r\n\x1a\n").unwrap();
|
||||
std::fs::OpenOptions::new().write(true).open(&path).unwrap().set_len(len).unwrap();
|
||||
};
|
||||
for len in [8, 64 << 10, 70_000, limit] {
|
||||
sized(len);
|
||||
assert_eq!(importer::read_path(&name, &named).unwrap().len() as u64, len);
|
||||
}
|
||||
sized(limit + 1);
|
||||
let e = importer::read_path(&name, &named).unwrap_err();
|
||||
assert!(e.to_string().contains("larger than") && e.to_string().contains("drawing.tuv"), "{e}");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -316,3 +316,23 @@ fn a_file_past_the_input_limit_is_refused() {
|
||||
assert!(s.documents().is_empty());
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A file past the importer's input limit whose start shows a built-in format opens as that format,
|
||||
/// whatever its extension; named, the importer's limit applies to it.
|
||||
#[test]
|
||||
fn a_built_in_file_past_the_input_limit_still_opens() {
|
||||
let dir = tmp_dir("largebuiltin");
|
||||
let mut s = Session::new();
|
||||
echo(&mut s, "test.largepng", r#"["tsp"]"#);
|
||||
let path = dir.join("big.tsp");
|
||||
let mut png = std::io::Cursor::new(vec![]);
|
||||
image::RgbaImage::new(2, 2).write_to(&mut png, image::ImageFormat::Png).unwrap();
|
||||
std::fs::write(&path, png.get_ref()).unwrap();
|
||||
std::fs::OpenOptions::new().write(true).open(&path).unwrap().set_len(vectorcraft_plugins::import::MAX_INPUT_BYTES as u64 + 1).unwrap();
|
||||
let e = s.execute("document.open", &json!({"path": path_of(&path), "importer": "plugin.test.largepng"})).unwrap_err();
|
||||
assert!(e.to_string().contains("larger than"), "{e}");
|
||||
assert!(s.documents().is_empty());
|
||||
let r = s.execute("document.open", &json!({"path": path_of(&path)})).unwrap();
|
||||
assert_eq!(r["format"], "png");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user