From 925300c15993685471dc80c43797fd7df18c4f61 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 18:05:05 -0800 Subject: [PATCH] HP-GL: a .plt file needs at least one instruction the reader knows A file read as HP-GL by its extension that holds only instructions of another set (`GC5;`) is now refused as "not an HP-GL file" instead of opening as an empty plot. At least one of the instructions the check reads must be one the reader knows. --- crates/cad/src/hpgl.rs | 14 +++++++++----- crates/cad/src/hpgl/tests.rs | 5 +++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/cad/src/hpgl.rs b/crates/cad/src/hpgl.rs index de2e6480b..5016e301f 100644 --- a/crates/cad/src/hpgl.rs +++ b/crates/cad/src/hpgl.rs @@ -71,16 +71,18 @@ pub fn is_hpgl(bytes: &[u8]) -> bool { /// Does `bytes` hold HP-GL instructions as [`is_hpgl`] checks? When `strict` is false, any of them /// may come first: a file picked by its extension may start with one a plot file seldom starts -/// with (`SI`, `DI`). +/// with (`SI`, `DI`). Either way, at least one of the instructions checked must be one the reader +/// knows. fn holds_instructions(bytes: &[u8], strict: bool) -> bool { let mut lex = Lexer::new(bytes); let mut terminator = lex::ETX; let mut first = 0; + let mut any_known = false; for n in 0..8 { if n > 0 && lex.pos() > first + 4096 { - return true; + return any_known; } - let Some(op) = lex.op(true) else { return n > 0 && lex.done() }; + let Some(op) = lex.op(true) else { return n > 0 && any_known && lex.done() }; if n == 0 { first = lex.pos(); } @@ -88,6 +90,7 @@ fn holds_instructions(bytes: &[u8], strict: bool) -> bool { return false; } let known = PLAYED.contains(&op) || IGNORED.contains(&op); + any_known |= known; match &op { b"CO" => { if !lex.comment() { @@ -114,11 +117,12 @@ fn holds_instructions(bytes: &[u8], strict: bool) -> bool { } } } - true + any_known } /// Read an HP-GL file. It may start with any instruction the reader knows, or one of another set -/// that a semicolon ends; the rest of [`is_hpgl`]'s check applies. +/// that a semicolon ends; the rest of [`is_hpgl`]'s check applies, and at least one instruction the +/// reader knows must be among those it checks. pub fn import(bytes: &[u8]) -> Result { if !holds_instructions(bytes, false) { return Err("not an HP-GL file".into()); diff --git a/crates/cad/src/hpgl/tests.rs b/crates/cad/src/hpgl/tests.rs index 873e2d8fb..9a350707a 100644 --- a/crates/cad/src/hpgl/tests.rs +++ b/crates/cad/src/hpgl/tests.rs @@ -451,6 +451,11 @@ fn a_plot_file_may_start_with_any_instruction() { for f in [&b"GC5"[..], b"SIhello world", b"set terminal png\n"] { assert_eq!(import(f).err().as_deref(), Some("not an HP-GL file"), "{:?}", String::from_utf8_lossy(f)); } + // Instructions of another set alone, however many or long, are not taken for HP-GL. + let long = [&b"GC"[..], "1".repeat(5000).as_bytes(), b";ZZ1;"].concat(); + for f in [&b"GC5;"[..], b"GC5;ZZ1;", b"GC5;ZZ1;YY2;XX3;WW4;VV5;UU6;TT7;", &long] { + assert_eq!(import(f).err().as_deref(), Some("not an HP-GL file"), "{:?}", String::from_utf8_lossy(f)); + } } #[test]