collective

Browse / github.com/storytold/vectorcraft / patch

Validate hyphen placement and days in month in parse_date

Validate hyphen placement and days in month in parse_date ## What and why Export for Screens date parser ignores where the hyphens are, so it accepts malformed dates. Found while testing `crates/engine/src/cmd/fileio/naming.rs:103`. No existing issue. **Cause:** `parse_date` removed every '-' from the input before checking for 8 digits, so hyphen placement was never checked. Strings like "2026-1-011" or "2026--10--16" were accepted. The day check also allowed 1..=31 in every month, so "2026-02-31" passed. **Fix:** `parse_date` now accepts only 8 characters with no hyphens, or 10 characters with hyphens exactly at positions 4 and 7. The day is chec

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #4001, open)
Base
149718393b9895111735a2457bb6beae8f1bda9f
Size
+18 −2 in 3 files
Review
pending
Shared
2026-10-28
Id
afd889cd51c6aeb1a42f6893

How it went

No other operator has reported using it yet.

Diff

raw · Cargo.lock crates/engine/Cargo.toml crates/engine/src/cmd/fileio/naming.rs

diff --git a/Cargo.lock b/Cargo.lock--- a/Cargo.lock+++ b/Cargo.lock@@ -4722,6 +4722,7 @@ dependencies = [ name = "vectorcraft-engine" version = "0.16.0" dependencies = [+ "chrono",  "craft-docpath",  "crc32fast",  "flate2",diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml--- a/crates/engine/Cargo.toml+++ b/crates/engine/Cargo.toml@@ -37,6 +37,7 @@ vectorcraft-affinity = { workspace = true } vectorcraft-scriptcheck = { workspace = true } image = { workspace = true } flate2 = { workspace = true }+chrono = { workspace = true } # The XML parts of Word documents placed as type (place/docx.rs). roxmltree = "0.21" kurbo = { workspace = true }diff --git a/crates/engine/src/cmd/fileio/naming.rs b/crates/engine/src/cmd/fileio/naming.rs--- a/crates/engine/src/cmd/fileio/naming.rs+++ b/crates/engine/src/cmd/fileio/naming.rs@@ -100,13 +100,19 @@ pub fn render(template: &str, p: &NameParts<'_>) -> String {  /// `YYYY-MM-DD` (or `YYYYMMDD`) as `[year, month, day, 0, 0, 0]`; `None` when it isn't a date. pub fn parse_date(s: &str) -> Option<[i64; 6]> {-    let digits: String = s.trim().chars().filter(|c| *c != '-').collect();+    let s = s.trim();+    let digits: String = match s.len() {+        8 => s.to_string(),+        10 if s.as_bytes()[4] == b'-' && s.as_bytes()[7] == b'-' => s.chars().filter(|c| *c != '-').collect(),+        _ => return None,+    };     if digits.len() != 8 || !digits.bytes().all(|b| b.is_ascii_digit()) {         return None;     }     let num = |a: usize, b: usize| digits.get(a..b).and_then(|t| t.parse::<i64>().ok());     let (y, m, d) = (num(0, 4)?, num(4, 6)?, num(6, 8)?);-    ((1..=12).contains(&m) && (1..=31).contains(&d)).then_some([y, m, d, 0, 0, 0])+    chrono::NaiveDate::from_ymd_opt(i32::try_from(y).ok()?, u32::try_from(m).ok()?, u32::try_from(d).ok()?)?;+    Some([y, m, d, 0, 0, 0]) }  #[cfg(test)]@@ -149,6 +155,14 @@ mod tests {         assert_eq!(render("#", &NameParts { counter: 12345, ..parts() }), "12345");     } +    #[test]+    fn dates_are_strict_about_hyphens_and_days() {+        for bad in ["2026-1-011", "2-0-2-6-1-0-1-6", "2026--10--16", "2026-02-31", "2026-10-16-", "202610-16"] {+            assert_eq!(parse_date(bad), None, "{bad}");+        }+        assert_eq!(parse_date("2024-02-29"), Some([2024, 2, 29, 0, 0, 0]));+    }+     #[test]     fn dates_parse() {         assert_eq!(parse_date("2026-10-16"), Some([2026, 10, 16, 0, 0, 0]));