catalog: slice captured dates on char boundaries so non-ASCII dates don't panic

This commit is contained in:
midasmatterhorn
2026-10-14 18:20:34 +03:00
parent 16dbb90499
commit 14d321c107
2 changed files with 24 additions and 4 deletions
+7 -4
View File
@@ -397,10 +397,13 @@ impl Catalog {
let mut years: BTreeMap<&str, (BTreeMap<&str, usize>, BTreeMap<&str, usize>)> = BTreeMap::new();
for p in self.photos().filter(|p| p.in_library()) {
let Some(d) = p.captured.as_deref() else { continue };
if d.len() >= 10 {
let (months, days) = years.entry(&d[..4]).or_default();
*months.entry(&d[..7]).or_default() += 1;
*days.entry(&d[..10]).or_default() += 1;
// `get`, not `[..]`: a corrupt or foreign `captured` (a Lightroom catalog's
// captureTime is copied verbatim) may not be ASCII, and slicing it on a byte index
// would panic — the same rule as `dates::group_label`.
if let (Some(year), Some(month), Some(day)) = (d.get(..4), d.get(..7), d.get(..10)) {
let (months, days) = years.entry(year).or_default();
*months.entry(month).or_default() += 1;
*days.entry(day).or_default() += 1;
}
}
let newest_first = |m: BTreeMap<&str, usize>| m.into_iter().rev().map(|(k, n)| (k.to_string(), n)).collect::<Vec<_>>();
+17
View File
@@ -123,6 +123,23 @@ fn filter_search_sort() {
assert_eq!(c.keywords(), vec![("ocean".to_string(), 1), ("summer".to_string(), 1)]);
}
/// A `captured` string that is not ASCII (the Lightroom importer copies `captureTime` into an
/// `AddPhoto` op verbatim, and a JSON snapshot is valid UTF-8 whatever it holds) must not be
/// sliced on a byte index in `date_groups`: grouping counts the well-formed dates and ignores
/// the rest instead of panicking on a char boundary.
#[test]
fn date_groups_ignores_a_non_ascii_captured() {
let mut c = Catalog::new();
let ok = photo(&mut c, "ok.jpg", "2026-05-01T10:00:00");
// `AddPhoto` takes the field as it is, like `library.importLightroom` building the op
let bad = photo(&mut c, "bad.jpg", "日本語日本語");
assert_ne!(ok, bad);
let g = c.date_groups();
assert_eq!(g.len(), 1, "the one well-formed date still groups");
assert_eq!((g[0].year.as_str(), g[0].count), ("2026", 1));
assert_eq!(g[0].days.iter().map(|d| d.1).sum::<usize>(), 1);
}
#[test]
fn snapshot_and_log_replay() {
let mut c = Catalog::new();